[FSRTL]
authorArt Yerkes <art.yerkes@gmail.com>
Wed, 29 Feb 2012 09:18:57 +0000 (09:18 +0000)
committerArt Yerkes <art.yerkes@gmail.com>
Wed, 29 Feb 2012 09:18:57 +0000 (09:18 +0000)
Import mostly-complete file locking implementation from arty-newcc.

svn path=/trunk/; revision=55915

reactos/ntoskrnl/fsrtl/filelock.c

index 18ea87a..08faf51 100644 (file)
 #define NDEBUG
 #include <debug.h>
 
+#define RESOURCE_OF_LOCK(L) ((PERESOURCE)&((L)->Unknown1))
+
 /* GLOBALS *******************************************************************/
 
 PAGED_LOOKASIDE_LIST FsRtlFileLockLookasideList;
 
+/* Note: this aligns the two types of lock entry structs so we can access the 
+   FILE_LOCK_INFO part in common.  Add elements after Shared if new stuff is needed.
+*/
+typedef union _COMBINED_LOCK_ELEMENT
+{
+       struct
+       {
+               LIST_ENTRY dummy;
+               FILE_SHARED_LOCK_ENTRY Shared;
+       };
+       FILE_EXCLUSIVE_LOCK_ENTRY Exclusive;
+}
+COMBINED_LOCK_ELEMENT, *PCOMBINED_LOCK_ELEMENT;
+
+typedef struct _LOCK_INFORMATION
+{
+       RTL_GENERIC_TABLE RangeTable;
+       IO_CSQ Csq;
+       KSPIN_LOCK CsqLock;
+       PFILE_LOCK BelongsTo;
+}
+LOCK_INFORMATION, *PLOCK_INFORMATION;
+
 /* PRIVATE FUNCTIONS *********************************************************/
 
+VOID
+NTAPI
+FsRtlCompleteLockIrpReal(IN PCOMPLETE_LOCK_IRP_ROUTINE CompleteRoutine,
+                         IN PVOID Context,
+                         IN PIRP Irp,
+                         IN NTSTATUS Status,
+                         OUT PNTSTATUS NewStatus,
+                         IN PFILE_OBJECT FileObject OPTIONAL);
+
+/* Generic table methods */
+
+static PVOID NTAPI LockAllocate(PRTL_GENERIC_TABLE Table, CLONG Bytes)
+{
+       PVOID Result;
+       Result = ExAllocatePoolWithTag(NonPagedPool, Bytes, 'FLCK');
+       DPRINT("LockAllocate(%d) => %p\n", Bytes, Result);
+       return Result;
+}
+
+static VOID NTAPI LockFree(PRTL_GENERIC_TABLE Table, PVOID Buffer)
+{
+       DPRINT("LockFree(%p)\n", Buffer);
+       ExFreePoolWithTag(Buffer, 'FLCK');
+}
+
+static RTL_GENERIC_COMPARE_RESULTS NTAPI LockCompare
+(PRTL_GENERIC_TABLE Table, PVOID PtrA, PVOID PtrB)
+{
+       PCOMBINED_LOCK_ELEMENT A = PtrA, B = PtrB;
+       RTL_GENERIC_COMPARE_RESULTS Result;
+       DPRINT("Starting to compare element %x to element %x\n", PtrA, PtrB);
+       Result =
+               (A->Exclusive.FileLock.EndingByte.QuadPart < 
+                B->Exclusive.FileLock.StartingByte.QuadPart) ? GenericLessThan :
+               (A->Exclusive.FileLock.StartingByte.QuadPart > 
+                B->Exclusive.FileLock.EndingByte.QuadPart) ? GenericGreaterThan :
+               GenericEqual;
+       DPRINT("Compare(%x:%x) %x-%x to %x-%x => %d\n",
+                  A,B,
+                  A->Exclusive.FileLock.StartingByte.LowPart, 
+                  A->Exclusive.FileLock.EndingByte.LowPart,
+                  B->Exclusive.FileLock.StartingByte.LowPart, 
+                  B->Exclusive.FileLock.EndingByte.LowPart,
+                  Result);
+       return Result;
+}
+
+/* CSQ methods */
+
+static NTSTATUS NTAPI LockInsertIrpEx
+(PIO_CSQ Csq,
+ PIRP Irp,
+ PVOID InsertContext)
+{
+       PCOMBINED_LOCK_ELEMENT LockElement = InsertContext;
+       InsertTailList(&LockElement->Exclusive.ListEntry, &Irp->Tail.Overlay.ListEntry);
+       return STATUS_SUCCESS;
+}
+
+static VOID NTAPI LockRemoveIrp(PIO_CSQ Csq, PIRP Irp)
+{
+       RemoveEntryList(&Irp->Tail.Overlay.ListEntry);
+}
+
+static PIRP NTAPI LockPeekNextIrp(PIO_CSQ Csq, PIRP Irp, PVOID PeekContext)
+{
+       // Context will be a COMBINED_LOCK_ELEMENT.  We're looking for a 
+       // lock that can be acquired, now that the lock matching PeekContext
+       // has been removed.
+       COMBINED_LOCK_ELEMENT LockElement;
+       PCOMBINED_LOCK_ELEMENT WhereUnlocked = PeekContext, Matching;
+       PLOCK_INFORMATION LockInfo = CONTAINING_RECORD(Csq, LOCK_INFORMATION, Csq);
+       PFILE_LOCK FileLock = LockInfo->BelongsTo;
+       if (!PeekContext)
+               return CONTAINING_RECORD
+                       (Irp->Tail.Overlay.ListEntry.Flink, 
+                        IRP, 
+                        Tail.Overlay.ListEntry);
+       else
+       {
+               PLIST_ENTRY Following;
+               if (!FileLock->LockInformation)
+               {
+                       return CONTAINING_RECORD
+                               (Irp->Tail.Overlay.ListEntry.Flink,
+                                IRP,
+                                Tail.Overlay.ListEntry);
+               }
+               for (Following = Irp->Tail.Overlay.ListEntry.Flink;
+                        Following != &WhereUnlocked->Exclusive.ListEntry;
+                        Following = Following->Flink)
+               {
+                       PIRP Irp = CONTAINING_RECORD(Following, IRP, Tail.Overlay.ListEntry);
+                       PIO_STACK_LOCATION IoStack = IoGetCurrentIrpStackLocation(Irp);
+                       LockElement.Exclusive.FileLock.StartingByte = 
+                               IoStack->Parameters.LockControl.ByteOffset;
+                       LockElement.Exclusive.FileLock.EndingByte.QuadPart = 
+                               LockElement.Exclusive.FileLock.StartingByte.QuadPart + 
+                               IoStack->Parameters.LockControl.Length->QuadPart;
+                       Matching = RtlLookupElementGenericTable
+                               (FileLock->LockInformation, &LockElement);
+                       if (!Matching)
+                       {
+                               // This IRP is fine...
+                               return Irp;
+                       }
+               }
+               return NULL;
+       }
+}
+
+static VOID NTAPI
+LockAcquireQueueLock(PIO_CSQ Csq, PKIRQL Irql)
+{
+       PLOCK_INFORMATION LockInfo = CONTAINING_RECORD(Csq, LOCK_INFORMATION, Csq);
+       KeAcquireSpinLock(&LockInfo->CsqLock, Irql);
+}
+
+static VOID NTAPI
+LockReleaseQueueLock(PIO_CSQ Csq, KIRQL Irql)
+{
+       PLOCK_INFORMATION LockInfo = CONTAINING_RECORD(Csq, LOCK_INFORMATION, Csq);
+       KeReleaseSpinLock(&LockInfo->CsqLock, Irql);
+}
+
+static VOID NTAPI
+LockCompleteCanceledIrp(PIO_CSQ Csq, PIRP Irp)
+{
+       NTSTATUS Status;
+       PLOCK_INFORMATION LockInfo = CONTAINING_RECORD(Csq, LOCK_INFORMATION, Csq);
+       FsRtlCompleteLockIrpReal
+               (LockInfo->BelongsTo->CompleteLockIrpRoutine,
+                NULL,
+                Irp,
+                STATUS_CANCELLED,
+                &Status,
+                NULL);
+}
+
 VOID
 NTAPI
 FsRtlCompleteLockIrpReal(IN PCOMPLETE_LOCK_IRP_ROUTINE CompleteRoutine,
@@ -55,8 +219,11 @@ NTAPI
 FsRtlGetNextFileLock(IN PFILE_LOCK FileLock,
                      IN BOOLEAN Restart)
 {
-    KeBugCheck(FILE_SYSTEM);
-    return NULL;
+       PCOMBINED_LOCK_ELEMENT Entry;
+       if (!FileLock->LockInformation) return NULL;
+       Entry = RtlEnumerateGenericTable(FileLock->LockInformation, Restart);
+       if (!Entry) return NULL;
+       else return &Entry->Exclusive.FileLock;
 }
 
 /*
@@ -78,32 +245,137 @@ FsRtlPrivateLock(IN PFILE_LOCK FileLock,
                  IN BOOLEAN AlreadySynchronized)
 {
     NTSTATUS Status;
+       COMBINED_LOCK_ELEMENT ToInsert;
+       PCOMBINED_LOCK_ELEMENT Conflict;
+       PLOCK_INFORMATION LockInfo;
+       BOOLEAN InsertedNew;
 
-    static BOOLEAN Warn;
-    if (!Warn++) DPRINT1("FsRtlPrivateLock() is stubplemented!\n");
+    DPRINT1("FsRtlPrivateLock() is stubplemented!\n");
+       ASSERT(AlreadySynchronized);
 
     /* Initialize the lock, if necessary */
     if (!FileLock->LockInformation)
     {
-        DPRINT("LockInformation is uninitialized!\n");
+               LockInfo = ExAllocatePool(PagedPool, sizeof(LOCK_INFORMATION));
+               FileLock->LockInformation = LockInfo;
+               if (!FileLock)
+                       return FALSE;
+
+               LockInfo->BelongsTo = FileLock;
+
+               RtlInitializeGenericTable
+                       (&LockInfo->RangeTable,
+                        LockCompare,
+                        LockAllocate,
+                        LockFree,
+                        NULL);
+
+               KeInitializeSpinLock(&LockInfo->CsqLock);
+
+               IoCsqInitializeEx
+                       (&LockInfo->Csq, 
+                        LockInsertIrpEx,
+                        LockRemoveIrp,
+                        LockPeekNextIrp,
+                        LockAcquireQueueLock,
+                        LockReleaseQueueLock,
+                        LockCompleteCanceledIrp);
     }
 
-    /* Assume all is cool, and lock is set */
-    IoStatus->Status = STATUS_SUCCESS;
-
-    if (Irp)
-    {
-        /* Complete the request */
-        FsRtlCompleteLockIrpReal(FileLock->CompleteLockIrpRoutine,
-                                 Context,
-                                 Irp,
-                                 IoStatus->Status,
-                                 &Status,
-                                 FileObject);
-
-        /* Update the status */
-        IoStatus->Status = Status;
-    }
+       LockInfo = FileLock->LockInformation;
+       ToInsert.Exclusive.FileLock.FileObject = FileObject;
+       ToInsert.Exclusive.FileLock.StartingByte = *FileOffset;
+       ToInsert.Exclusive.FileLock.EndingByte.QuadPart = FileOffset->QuadPart + Length->QuadPart;
+       ToInsert.Exclusive.FileLock.ProcessId = Process->UniqueProcessId;
+       ToInsert.Exclusive.FileLock.Key = Key;
+       ToInsert.Exclusive.FileLock.ExclusiveLock = ExclusiveLock;
+
+       Conflict = RtlInsertElementGenericTable
+               (FileLock->LockInformation,
+                &ToInsert,
+                sizeof(ToInsert),
+                &InsertedNew);
+
+       if (Conflict && !InsertedNew)
+       {
+               if (Conflict->Exclusive.FileLock.ExclusiveLock || ExclusiveLock)
+               {
+                       if (FailImmediately)
+                       {
+                               IoStatus->Status = STATUS_FILE_LOCK_CONFLICT;
+                               if (Irp)
+                               {
+                                       FsRtlCompleteLockIrpReal
+                                               (FileLock->CompleteLockIrpRoutine,
+                                                Context,
+                                                Irp,
+                                                IoStatus->Status,
+                                                &Status,
+                                                FileObject);
+                               }
+                               return FALSE;
+                       }
+                       else
+                       {
+                               IoStatus->Status = STATUS_PENDING;
+                               if (Irp)
+                               {
+                                       IoMarkIrpPending(Irp);
+                                       IoCsqInsertIrpEx
+                                               (&LockInfo->Csq,
+                                                Irp,
+                                                NULL,
+                                                NULL);
+                               }
+                       }
+                       return FALSE;
+               }
+               else
+               {
+                       IoStatus->Status = STATUS_SUCCESS;
+                       if (Irp)
+                       {
+                               FsRtlCompleteLockIrpReal
+                                       (FileLock->CompleteLockIrpRoutine,
+                                        Context,
+                                        Irp,
+                                        IoStatus->Status,
+                                        &Status,
+                                        FileObject);
+                       }
+                       return FALSE;
+               }
+       }
+       else if (!Conflict)
+       {
+               /* Conflict here is (or would be) the newly inserted element, but we ran
+                * out of space probably. */
+               IoStatus->Status = STATUS_NO_MEMORY;
+               return FALSE;
+       }
+       else
+       {
+               /* Assume all is cool, and lock is set */
+               IoStatus->Status = STATUS_SUCCESS;
+       
+               // Initialize our resource ... We'll use this to mediate access to the
+               // irp queue.
+               ExInitializeResourceLite(RESOURCE_OF_LOCK(&Conflict->Exclusive));
+       
+               if (Irp)
+               {
+                       /* Complete the request */
+                       FsRtlCompleteLockIrpReal(FileLock->CompleteLockIrpRoutine,
+                                                                        Context,
+                                                                        Irp,
+                                                                        IoStatus->Status,
+                                                                        &Status,
+                                                                        FileObject);
+                       
+                       /* Update the status */
+                       IoStatus->Status = Status;
+               }
+       }
 
     return TRUE;
 }
@@ -116,8 +388,20 @@ NTAPI
 FsRtlCheckLockForReadAccess(IN PFILE_LOCK FileLock,
                             IN PIRP Irp)
 {
-    KeBugCheck(FILE_SYSTEM);
-    return FALSE;
+       PIO_STACK_LOCATION IoStack = IoGetCurrentIrpStackLocation(Irp);
+       COMBINED_LOCK_ELEMENT ToFind;
+       PCOMBINED_LOCK_ELEMENT Found;
+       if (!FileLock->LockInformation) return TRUE;
+       ToFind.Exclusive.FileLock.StartingByte = IoStack->Parameters.Read.ByteOffset;
+       ToFind.Exclusive.FileLock.EndingByte.QuadPart = 
+               ToFind.Exclusive.FileLock.StartingByte.QuadPart + 
+               IoStack->Parameters.Read.Length;
+       Found = RtlLookupElementGenericTable
+               (FileLock->LockInformation,
+                &ToFind);
+       if (!Found) return TRUE;
+       return !Found->Exclusive.FileLock.ExclusiveLock || 
+               IoStack->Parameters.Read.Key == Found->Exclusive.FileLock.Key;
 }
 
 /*
@@ -128,8 +412,20 @@ NTAPI
 FsRtlCheckLockForWriteAccess(IN PFILE_LOCK FileLock,
                              IN PIRP Irp)
 {
-    KeBugCheck(FILE_SYSTEM);
-    return FALSE;
+       PIO_STACK_LOCATION IoStack = IoGetCurrentIrpStackLocation(Irp);
+       COMBINED_LOCK_ELEMENT ToFind;
+       PCOMBINED_LOCK_ELEMENT Found;
+       PEPROCESS Process = Irp->Tail.Overlay.Thread->ThreadsProcess;
+       if (!FileLock->LockInformation) return TRUE;
+       ToFind.Exclusive.FileLock.StartingByte = IoStack->Parameters.Write.ByteOffset;
+       ToFind.Exclusive.FileLock.EndingByte.QuadPart = 
+               ToFind.Exclusive.FileLock.StartingByte.QuadPart + 
+               IoStack->Parameters.Write.Length;
+       Found = RtlLookupElementGenericTable
+               (FileLock->LockInformation,
+                &ToFind);
+       if (!Found) return TRUE;
+       return Process->UniqueProcessId == Found->Exclusive.FileLock.ProcessId;
 }
 
 /*
@@ -144,8 +440,19 @@ FsRtlFastCheckLockForRead(IN PFILE_LOCK FileLock,
                           IN PFILE_OBJECT FileObject,
                           IN PVOID Process)
 {
-    KeBugCheck(FILE_SYSTEM);
-    return FALSE;
+       PEPROCESS EProcess = Process;
+       COMBINED_LOCK_ELEMENT ToFind;
+       PCOMBINED_LOCK_ELEMENT Found;
+       ToFind.Exclusive.FileLock.StartingByte = *FileOffset;
+       ToFind.Exclusive.FileLock.EndingByte.QuadPart = 
+               FileOffset->QuadPart + Length->QuadPart;
+       if (!FileLock->LockInformation) return TRUE;
+       Found = RtlLookupElementGenericTable
+               (FileLock->LockInformation,
+                &ToFind);
+       if (!Found || !Found->Exclusive.FileLock.ExclusiveLock) return TRUE;
+       return Found->Exclusive.FileLock.Key == Key && 
+               Found->Exclusive.FileLock.ProcessId == EProcess->UniqueProcessId;
 }
 
 /*
@@ -160,8 +467,19 @@ FsRtlFastCheckLockForWrite(IN PFILE_LOCK FileLock,
                            IN PFILE_OBJECT FileObject,
                            IN PVOID Process)
 {
-    KeBugCheck(FILE_SYSTEM);
-    return FALSE;
+       PEPROCESS EProcess = Process;
+       COMBINED_LOCK_ELEMENT ToFind;
+       PCOMBINED_LOCK_ELEMENT Found;
+       ToFind.Exclusive.FileLock.StartingByte = *FileOffset;
+       ToFind.Exclusive.FileLock.EndingByte.QuadPart = 
+               FileOffset->QuadPart + Length->QuadPart;
+       if (!FileLock->LockInformation) return TRUE;
+       Found = RtlLookupElementGenericTable
+               (FileLock->LockInformation,
+                &ToFind);
+       if (!Found) return TRUE;
+       return Found->Exclusive.FileLock.Key == Key && 
+               Found->Exclusive.FileLock.ProcessId == EProcess->UniqueProcessId;
 }
 
 /*
@@ -178,10 +496,38 @@ FsRtlFastUnlockSingle(IN PFILE_LOCK FileLock,
                       IN PVOID Context OPTIONAL,
                       IN BOOLEAN AlreadySynchronized)
 {
-    static BOOLEAN Warn;
-    if (!Warn++) DPRINT1("FsRtlFastUnlockSingle() is stubplemented!\n");
-
-    return STATUS_SUCCESS;
+       COMBINED_LOCK_ELEMENT Find;
+       PCOMBINED_LOCK_ELEMENT Entry;
+       PIRP NextMatchingLockIrp;
+       PLOCK_INFORMATION InternalInfo = FileLock->LockInformation;
+       // The region to unlock must correspond exactly to a previously locked region
+       // -- msdn
+       ASSERT(AlreadySynchronized);
+       Find.Exclusive.FileLock.StartingByte = *FileOffset;
+       Find.Exclusive.FileLock.EndingByte.QuadPart = 
+               FileOffset->QuadPart + Length->QuadPart;
+       Entry = RtlLookupElementGenericTable(&InternalInfo->RangeTable, &Find);
+       if (!Entry) return STATUS_RANGE_NOT_LOCKED;
+       if (Entry->Exclusive.FileLock.Key != Key || 
+               Entry->Exclusive.FileLock.ProcessId != Process->UniqueProcessId)
+               return STATUS_RANGE_NOT_LOCKED;
+       if (Entry->Exclusive.FileLock.StartingByte.QuadPart != FileOffset->QuadPart ||
+               Entry->Exclusive.FileLock.EndingByte.QuadPart != 
+               FileOffset->QuadPart + Length->QuadPart)
+               return STATUS_RANGE_NOT_LOCKED;
+       // this is definitely the thing we want
+       RtlCopyMemory(&Find, Entry, sizeof(Find));
+       RtlDeleteElementGenericTable(&InternalInfo->RangeTable, Entry);
+       NextMatchingLockIrp = IoCsqRemoveNextIrp(&InternalInfo->Csq, &Find);
+       if (NextMatchingLockIrp)
+       {
+               // Got a new lock irp... try to do the new lock operation
+               // Note that we pick an operation that would succeed at the time
+               // we looked, but can't guarantee that it won't just be re-queued
+               // because somebody else snatched part of the range in a new thread.
+               FsRtlProcessFileLock(InternalInfo->BelongsTo, NextMatchingLockIrp, NULL);
+       }
+       return STATUS_SUCCESS;
 }
 
 /*
@@ -194,8 +540,32 @@ FsRtlFastUnlockAll(IN PFILE_LOCK FileLock,
                    IN PEPROCESS Process,
                    IN PVOID Context OPTIONAL)
 {
-    KeBugCheck(FILE_SYSTEM);
-    return STATUS_UNSUCCESSFUL;
+       PCOMBINED_LOCK_ELEMENT Entry;
+       PRTL_GENERIC_TABLE InternalInfo = FileLock->LockInformation;
+
+       // XXX Synchronize somehow
+       if (!FileLock->LockInformation) return STATUS_RANGE_NOT_LOCKED; // no locks
+       for (Entry = RtlEnumerateGenericTable(InternalInfo, TRUE);
+                        Entry;
+                Entry = RtlEnumerateGenericTable(InternalInfo, FALSE))
+       {
+               LARGE_INTEGER Length;
+               // We'll take the first one to be the list head, and free the others first...
+               Length.QuadPart = 
+                       Entry->Exclusive.FileLock.EndingByte.QuadPart - 
+                       Entry->Exclusive.FileLock.StartingByte.QuadPart;
+               FsRtlFastUnlockSingle
+                       (FileLock, 
+                        Entry->Exclusive.FileLock.FileObject,
+                        &Entry->Exclusive.FileLock.StartingByte,
+                        &Length,
+                        Entry->Exclusive.FileLock.ProcessId,
+                        Entry->Exclusive.FileLock.Key,
+                        Context,
+                        TRUE);
+       }
+
+    return STATUS_SUCCESS;
 }
 
 /*
@@ -209,8 +579,36 @@ FsRtlFastUnlockAllByKey(IN PFILE_LOCK FileLock,
                         IN ULONG Key,
                         IN PVOID Context OPTIONAL)
 {
-    KeBugCheck(FILE_SYSTEM);
-    return STATUS_UNSUCCESSFUL;
+       PCOMBINED_LOCK_ELEMENT Entry;
+       PRTL_GENERIC_TABLE InternalInfo = FileLock->LockInformation;
+
+       // XXX Synchronize somehow
+       if (!FileLock->LockInformation) return STATUS_RANGE_NOT_LOCKED; // no locks
+       for (Entry = RtlEnumerateGenericTable(InternalInfo, TRUE);
+                        Entry;
+                Entry = RtlEnumerateGenericTable(InternalInfo, FALSE))
+       {
+               LARGE_INTEGER Length;
+               // We'll take the first one to be the list head, and free the others first...
+               Length.QuadPart = 
+                       Entry->Exclusive.FileLock.EndingByte.QuadPart - 
+                       Entry->Exclusive.FileLock.StartingByte.QuadPart;
+               if (Entry->Exclusive.FileLock.Key == Key && 
+                       Entry->Exclusive.FileLock.ProcessId == Process->UniqueProcessId)
+               {
+                       FsRtlFastUnlockSingle
+                               (FileLock, 
+                                Entry->Exclusive.FileLock.FileObject,
+                                &Entry->Exclusive.FileLock.StartingByte,
+                                &Length,
+                                Entry->Exclusive.FileLock.ProcessId,
+                                Entry->Exclusive.FileLock.Key,
+                                Context,
+                                TRUE);
+               }
+       }
+
+    return STATUS_SUCCESS;
 }
 
 /*
@@ -355,7 +753,12 @@ VOID
 NTAPI
 FsRtlUninitializeFileLock(IN PFILE_LOCK FileLock)
 {
-    return;
+       if (FileLock->LockInformation)
+       {
+               ASSERT(!RtlNumberGenericTableElements(FileLock->LockInformation));
+               ExFreePool(FileLock->LockInformation);
+               FileLock->LockInformation = NULL;
+       }
 }
 
 /*
@@ -393,4 +796,3 @@ FsRtlFreeFileLock(IN PFILE_LOCK FileLock)
     FsRtlUninitializeFileLock(FileLock);
     ExFreeToPagedLookasideList(&FsRtlFileLockLookasideList, FileLock);
 }
-