/* * PROJECT: ReactOS Kernel * LICENSE: GPL - See COPYING in the top level directory * FILE: ntoskrnl/fsrtl/filelock.c * PURPOSE: File Locking implementation for File System Drivers * PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org) */ /* INCLUDES ******************************************************************/ #include #define NDEBUG #include /* 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; LIST_ENTRY CsqList; PFILE_LOCK BelongsTo; LIST_ENTRY SharedLocks; ULONG Generation; } LOCK_INFORMATION, *PLOCK_INFORMATION; typedef struct _LOCK_SHARED_RANGE { LIST_ENTRY Entry; LARGE_INTEGER Start, End; ULONG Key; PVOID ProcessId; } LOCK_SHARED_RANGE, *PLOCK_SHARED_RANGE; #define TAG_TABLE 'LTAB' #define TAG_RANGE 'FSRA' #define TAG_FLOCK 'FLCK' /* 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, TAG_TABLE); DPRINT("LockAllocate(%lu) => %p\n", Bytes, Result); return Result; } static VOID NTAPI LockFree(PRTL_GENERIC_TABLE Table, PVOID Buffer) { DPRINT("LockFree(%p)\n", Buffer); ExFreePoolWithTag(Buffer, TAG_TABLE); } 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; #if 0 DPRINT("Starting to compare element %x to element %x\n", PtrA, PtrB); #endif /* Match if we overlap */ if (((A->Exclusive.FileLock.StartingByte.QuadPart < B->Exclusive.FileLock.EndingByte.QuadPart) && (A->Exclusive.FileLock.StartingByte.QuadPart >= B->Exclusive.FileLock.StartingByte.QuadPart)) || ((B->Exclusive.FileLock.StartingByte.QuadPart < A->Exclusive.FileLock.EndingByte.QuadPart) && (B->Exclusive.FileLock.StartingByte.QuadPart >= A->Exclusive.FileLock.StartingByte.QuadPart))) return GenericEqual; /* Otherwise, key on the starting byte */ Result = (A->Exclusive.FileLock.StartingByte.QuadPart < B->Exclusive.FileLock.StartingByte.QuadPart) ? GenericLessThan : (A->Exclusive.FileLock.StartingByte.QuadPart > B->Exclusive.FileLock.StartingByte.QuadPart) ? GenericGreaterThan : GenericEqual; #if 0 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); #endif return Result; } /* CSQ methods */ static NTSTATUS NTAPI LockInsertIrpEx (PIO_CSQ Csq, PIRP Irp, PVOID InsertContext) { PLOCK_INFORMATION LockInfo = CONTAINING_RECORD(Csq, LOCK_INFORMATION, Csq); InsertTailList(&LockInfo->CsqList, &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 WhereUnlock = PeekContext; PLOCK_INFORMATION LockInfo = CONTAINING_RECORD(Csq, LOCK_INFORMATION, Csq); PLIST_ENTRY Following; DPRINT("PeekNextIrp(IRP %p, Context %p)\n", Irp, PeekContext); if (!Irp) { Following = LockInfo->CsqList.Flink; } else Following = Irp->Tail.Overlay.ListEntry.Flink; DPRINT("ListEntry %p Head %p\n", Following, &LockInfo->CsqList); for (; Following != &LockInfo->CsqList; Following = Following->Flink) { PIO_STACK_LOCATION IoStack; BOOLEAN Matching; Irp = CONTAINING_RECORD(Following, IRP, Tail.Overlay.ListEntry); DPRINT("Irp %p\n", Irp); 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; /* If a context was specified, it's a range to check to unlock */ if (WhereUnlock) { Matching = LockCompare (&LockInfo->RangeTable, &LockElement, WhereUnlock) != GenericEqual; } /* Else get any completable IRP */ else { Matching = FALSE; } if (!Matching) { // This IRP is fine... DPRINT("Returning the IRP %p\n", Irp); return Irp; } } DPRINT("Return NULL\n"); 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); DPRINT("Complete cancelled IRP %p Status %x\n", Irp, STATUS_CANCELLED); FsRtlCompleteLockIrpReal (LockInfo->BelongsTo->CompleteLockIrpRoutine, NULL, Irp, STATUS_CANCELLED, &Status, NULL); } 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) { /* Check if we have a complete routine */ Irp->IoStatus.Information = 0; if (CompleteRoutine) { /* Check if we have a file object */ if (FileObject) FileObject->LastLock = NULL; /* Set the I/O Status and do completion */ Irp->IoStatus.Status = Status; DPRINT("Calling completion routine %p Status %x\n", Irp, Status); *NewStatus = CompleteRoutine(Context, Irp); } else { /* Otherwise do a normal I/O complete request */ DPRINT("Completing IRP %p Status %x\n", Irp, Status); FsRtlCompleteRequest(Irp, Status); *NewStatus = Status; } } /* PUBLIC FUNCTIONS **********************************************************/ /* * @implemented */ PFILE_LOCK_INFO NTAPI FsRtlGetNextFileLock(IN PFILE_LOCK FileLock, IN BOOLEAN Restart) { PCOMBINED_LOCK_ELEMENT Entry; if (!FileLock->LockInformation) return NULL; Entry = RtlEnumerateGenericTable(FileLock->LockInformation, Restart); if (!Entry) return NULL; else return &Entry->Exclusive.FileLock; } VOID NTAPI FsRtlpExpandLockElement (PCOMBINED_LOCK_ELEMENT ToExpand, PCOMBINED_LOCK_ELEMENT Conflict) { if (ToExpand->Exclusive.FileLock.StartingByte.QuadPart > Conflict->Exclusive.FileLock.StartingByte.QuadPart) { ToExpand->Exclusive.FileLock.StartingByte = Conflict->Exclusive.FileLock.StartingByte; } if (ToExpand->Exclusive.FileLock.EndingByte.QuadPart < Conflict->Exclusive.FileLock.EndingByte.QuadPart) { ToExpand->Exclusive.FileLock.EndingByte = Conflict->Exclusive.FileLock.EndingByte; } } /* This function expands the conflicting range Conflict by removing and reinserting it, then adds a shared range of the same size */ PCOMBINED_LOCK_ELEMENT NTAPI FsRtlpRebuildSharedLockRange (PFILE_LOCK FileLock, PLOCK_INFORMATION LockInfo, PCOMBINED_LOCK_ELEMENT Conflict) { /* Starting at Conflict->StartingByte and going to Conflict->EndingByte * capture and expand a shared range from the shared range list. * Finish when we've incorporated all overlapping shared regions. */ BOOLEAN InsertedNew = FALSE, RemovedOld; COMBINED_LOCK_ELEMENT NewElement = *Conflict; PCOMBINED_LOCK_ELEMENT Entry; while ((Entry = RtlLookupElementGenericTable (FileLock->LockInformation, &NewElement))) { FsRtlpExpandLockElement(&NewElement, Entry); RemovedOld = RtlDeleteElementGenericTable (&LockInfo->RangeTable, Entry); ASSERT(RemovedOld); } Conflict = RtlInsertElementGenericTable (&LockInfo->RangeTable, &NewElement, sizeof(NewElement), &InsertedNew); ASSERT(InsertedNew); return Conflict; } /* * @implemented */ BOOLEAN NTAPI FsRtlPrivateLock(IN PFILE_LOCK FileLock, IN PFILE_OBJECT FileObject, IN PLARGE_INTEGER FileOffset, IN PLARGE_INTEGER Length, IN PEPROCESS Process, IN ULONG Key, IN BOOLEAN FailImmediately, IN BOOLEAN ExclusiveLock, OUT PIO_STATUS_BLOCK IoStatus, IN PIRP Irp OPTIONAL, IN PVOID Context OPTIONAL, IN BOOLEAN AlreadySynchronized) { NTSTATUS Status; COMBINED_LOCK_ELEMENT ToInsert; PCOMBINED_LOCK_ELEMENT Conflict; PLOCK_INFORMATION LockInfo; PLOCK_SHARED_RANGE NewSharedRange; BOOLEAN InsertedNew; ULARGE_INTEGER UnsignedStart; ULARGE_INTEGER UnsignedEnd; DPRINT("FsRtlPrivateLock(%wZ, Offset %08x%08x (%d), Length %08x%08x (%d), Key %x, FailImmediately %u, Exclusive %u)\n", &FileObject->FileName, FileOffset->HighPart, FileOffset->LowPart, (int)FileOffset->QuadPart, Length->HighPart, Length->LowPart, (int)Length->QuadPart, Key, FailImmediately, ExclusiveLock); UnsignedStart.QuadPart = FileOffset->QuadPart; UnsignedEnd.QuadPart = FileOffset->QuadPart + Length->QuadPart; if (UnsignedEnd.QuadPart < UnsignedStart.QuadPart) { DPRINT("File offset out of range\n"); IoStatus->Status = STATUS_INVALID_PARAMETER; if (Irp) { DPRINT("Complete lock %p Status %x\n", Irp, IoStatus->Status); FsRtlCompleteLockIrpReal (FileLock->CompleteLockIrpRoutine, Context, Irp, IoStatus->Status, &Status, FileObject); } return FALSE; } /* Initialize the lock, if necessary */ if (!FileLock->LockInformation) { LockInfo = ExAllocatePoolWithTag(NonPagedPool, sizeof(LOCK_INFORMATION), TAG_FLOCK); if (!LockInfo) { IoStatus->Status = STATUS_NO_MEMORY; return FALSE; } FileLock->LockInformation = LockInfo; LockInfo->BelongsTo = FileLock; InitializeListHead(&LockInfo->SharedLocks); RtlInitializeGenericTable (&LockInfo->RangeTable, LockCompare, LockAllocate, LockFree, NULL); KeInitializeSpinLock(&LockInfo->CsqLock); InitializeListHead(&LockInfo->CsqList); IoCsqInitializeEx (&LockInfo->Csq, LockInsertIrpEx, LockRemoveIrp, LockPeekNextIrp, LockAcquireQueueLock, LockReleaseQueueLock, LockCompleteCanceledIrp); } 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; 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) { DPRINT("Conflict %08x%08x:%08x%08x Exc %u (Want Exc %u)\n", Conflict->Exclusive.FileLock.StartingByte.HighPart, Conflict->Exclusive.FileLock.StartingByte.LowPart, Conflict->Exclusive.FileLock.EndingByte.HighPart, Conflict->Exclusive.FileLock.EndingByte.LowPart, Conflict->Exclusive.FileLock.ExclusiveLock, ExclusiveLock); if (FailImmediately) { DPRINT("STATUS_FILE_LOCK_CONFLICT\n"); IoStatus->Status = STATUS_FILE_LOCK_CONFLICT; if (Irp) { DPRINT("STATUS_FILE_LOCK_CONFLICT: Complete\n"); FsRtlCompleteLockIrpReal (FileLock->CompleteLockIrpRoutine, Context, Irp, IoStatus->Status, &Status, FileObject); } return FALSE; } else { IoStatus->Status = STATUS_PENDING; if (Irp) { Irp->IoStatus.Information = LockInfo->Generation; IoMarkIrpPending(Irp); IoCsqInsertIrpEx (&LockInfo->Csq, Irp, NULL, NULL); } } return FALSE; } else { ULONG i; /* We know of at least one lock in range that's shared. We need to * find out if any more exist and any are exclusive. */ for (i = 0; i < RtlNumberGenericTableElements(&LockInfo->RangeTable); i++) { Conflict = RtlGetElementGenericTable(&LockInfo->RangeTable, i); /* The first argument will be inserted as a shared range */ if (Conflict && (LockCompare(&LockInfo->RangeTable, Conflict, &ToInsert) == GenericEqual)) { if (Conflict->Exclusive.FileLock.ExclusiveLock) { /* Found an exclusive match */ if (FailImmediately) { IoStatus->Status = STATUS_FILE_LOCK_CONFLICT; DPRINT("STATUS_FILE_LOCK_CONFLICT\n"); if (Irp) { DPRINT("STATUS_FILE_LOCK_CONFLICT: Complete\n"); FsRtlCompleteLockIrpReal (FileLock->CompleteLockIrpRoutine, Context, Irp, IoStatus->Status, &Status, FileObject); } } else { IoStatus->Status = STATUS_PENDING; if (Irp) { IoMarkIrpPending(Irp); IoCsqInsertIrpEx (&LockInfo->Csq, Irp, NULL, NULL); } } return FALSE; } } } DPRINT("Overlapping shared lock %wZ %08x%08x %08x%08x\n", &FileObject->FileName, Conflict->Exclusive.FileLock.StartingByte.HighPart, Conflict->Exclusive.FileLock.StartingByte.LowPart, Conflict->Exclusive.FileLock.EndingByte.HighPart, Conflict->Exclusive.FileLock.EndingByte.LowPart); Conflict = FsRtlpRebuildSharedLockRange(FileLock, LockInfo, &ToInsert); if (!Conflict) { IoStatus->Status = STATUS_NO_MEMORY; if (Irp) { FsRtlCompleteLockIrpReal (FileLock->CompleteLockIrpRoutine, Context, Irp, IoStatus->Status, &Status, FileObject); } } /* We got here because there were only overlapping shared locks */ /* A shared lock is both a range *and* a list entry. Insert the entry here. */ DPRINT("Adding shared lock %wZ\n", &FileObject->FileName); NewSharedRange = ExAllocatePoolWithTag(NonPagedPool, sizeof(*NewSharedRange), TAG_RANGE); if (!NewSharedRange) { IoStatus->Status = STATUS_NO_MEMORY; if (Irp) { FsRtlCompleteLockIrpReal (FileLock->CompleteLockIrpRoutine, Context, Irp, IoStatus->Status, &Status, FileObject); } return FALSE; } DPRINT("Adding shared lock %wZ\n", &FileObject->FileName); NewSharedRange->Start = *FileOffset; NewSharedRange->End.QuadPart = FileOffset->QuadPart + Length->QuadPart; NewSharedRange->Key = Key; NewSharedRange->ProcessId = ToInsert.Exclusive.FileLock.ProcessId; InsertTailList(&LockInfo->SharedLocks, &NewSharedRange->Entry); DPRINT("Acquired shared lock %wZ %08x%08x %08x%08x\n", &FileObject->FileName, Conflict->Exclusive.FileLock.StartingByte.HighPart, Conflict->Exclusive.FileLock.StartingByte.LowPart, Conflict->Exclusive.FileLock.EndingByte.HighPart, Conflict->Exclusive.FileLock.EndingByte.LowPart); IoStatus->Status = STATUS_SUCCESS; if (Irp) { FsRtlCompleteLockIrpReal (FileLock->CompleteLockIrpRoutine, Context, Irp, IoStatus->Status, &Status, FileObject); } return TRUE; } } 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; if (Irp) { FsRtlCompleteLockIrpReal (FileLock->CompleteLockIrpRoutine, Context, Irp, IoStatus->Status, &Status, FileObject); } return FALSE; } else { DPRINT("Inserted new lock %wZ %08x%08x %08x%08x exclusive %u\n", &FileObject->FileName, Conflict->Exclusive.FileLock.StartingByte.HighPart, Conflict->Exclusive.FileLock.StartingByte.LowPart, Conflict->Exclusive.FileLock.EndingByte.HighPart, Conflict->Exclusive.FileLock.EndingByte.LowPart, Conflict->Exclusive.FileLock.ExclusiveLock); if (!ExclusiveLock) { NewSharedRange = ExAllocatePoolWithTag(NonPagedPool, sizeof(*NewSharedRange), TAG_RANGE); if (!NewSharedRange) { IoStatus->Status = STATUS_NO_MEMORY; if (Irp) { FsRtlCompleteLockIrpReal (FileLock->CompleteLockIrpRoutine, Context, Irp, IoStatus->Status, &Status, FileObject); } return FALSE; } DPRINT("Adding shared lock %wZ\n", &FileObject->FileName); NewSharedRange->Start = *FileOffset; NewSharedRange->End.QuadPart = FileOffset->QuadPart + Length->QuadPart; NewSharedRange->Key = Key; NewSharedRange->ProcessId = Process; InsertTailList(&LockInfo->SharedLocks, &NewSharedRange->Entry); } /* 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; } } return TRUE; } /* * @implemented */ BOOLEAN NTAPI FsRtlCheckLockForReadAccess(IN PFILE_LOCK FileLock, IN PIRP Irp) { BOOLEAN Result; PIO_STACK_LOCATION IoStack = IoGetCurrentIrpStackLocation(Irp); COMBINED_LOCK_ELEMENT ToFind; PCOMBINED_LOCK_ELEMENT Found; DPRINT("CheckLockForReadAccess(%wZ, Offset %08x%08x, Length %x)\n", &IoStack->FileObject->FileName, IoStack->Parameters.Read.ByteOffset.HighPart, IoStack->Parameters.Read.ByteOffset.LowPart, IoStack->Parameters.Read.Length); if (!FileLock->LockInformation) { DPRINT("CheckLockForReadAccess(%wZ) => TRUE\n", &IoStack->FileObject->FileName); 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) { DPRINT("CheckLockForReadAccess(%wZ) => TRUE\n", &IoStack->FileObject->FileName); return TRUE; } Result = !Found->Exclusive.FileLock.ExclusiveLock || IoStack->Parameters.Read.Key == Found->Exclusive.FileLock.Key; DPRINT("CheckLockForReadAccess(%wZ) => %s\n", &IoStack->FileObject->FileName, Result ? "TRUE" : "FALSE"); return Result; } /* * @implemented */ BOOLEAN NTAPI FsRtlCheckLockForWriteAccess(IN PFILE_LOCK FileLock, IN PIRP Irp) { BOOLEAN Result; PIO_STACK_LOCATION IoStack = IoGetCurrentIrpStackLocation(Irp); COMBINED_LOCK_ELEMENT ToFind; PCOMBINED_LOCK_ELEMENT Found; PEPROCESS Process = Irp->Tail.Overlay.Thread->ThreadsProcess; DPRINT("CheckLockForWriteAccess(%wZ, Offset %08x%08x, Length %x)\n", &IoStack->FileObject->FileName, IoStack->Parameters.Write.ByteOffset.HighPart, IoStack->Parameters.Write.ByteOffset.LowPart, IoStack->Parameters.Write.Length); if (!FileLock->LockInformation) { DPRINT("CheckLockForWriteAccess(%wZ) => TRUE\n", &IoStack->FileObject->FileName); 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) { DPRINT("CheckLockForWriteAccess(%wZ) => TRUE\n", &IoStack->FileObject->FileName); return TRUE; } Result = Process == Found->Exclusive.FileLock.ProcessId; DPRINT("CheckLockForWriteAccess(%wZ) => %s\n", &IoStack->FileObject->FileName, Result ? "TRUE" : "FALSE"); return Result; } /* * @implemented */ BOOLEAN NTAPI FsRtlFastCheckLockForRead(IN PFILE_LOCK FileLock, IN PLARGE_INTEGER FileOffset, IN PLARGE_INTEGER Length, IN ULONG Key, IN PFILE_OBJECT FileObject, IN PVOID Process) { PEPROCESS EProcess = Process; COMBINED_LOCK_ELEMENT ToFind; PCOMBINED_LOCK_ELEMENT Found; DPRINT("FsRtlFastCheckLockForRead(%wZ, Offset %08x%08x, Length %08x%08x, Key %x)\n", &FileObject->FileName, FileOffset->HighPart, FileOffset->LowPart, Length->HighPart, Length->LowPart, Key); 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; } /* * @implemented */ BOOLEAN NTAPI FsRtlFastCheckLockForWrite(IN PFILE_LOCK FileLock, IN PLARGE_INTEGER FileOffset, IN PLARGE_INTEGER Length, IN ULONG Key, IN PFILE_OBJECT FileObject, IN PVOID Process) { BOOLEAN Result; PEPROCESS EProcess = Process; COMBINED_LOCK_ELEMENT ToFind; PCOMBINED_LOCK_ELEMENT Found; DPRINT("FsRtlFastCheckLockForWrite(%wZ, Offset %08x%08x, Length %08x%08x, Key %x)\n", &FileObject->FileName, FileOffset->HighPart, FileOffset->LowPart, Length->HighPart, Length->LowPart, Key); ToFind.Exclusive.FileLock.StartingByte = *FileOffset; ToFind.Exclusive.FileLock.EndingByte.QuadPart = FileOffset->QuadPart + Length->QuadPart; if (!FileLock->LockInformation) { DPRINT("CheckForWrite(%wZ) => TRUE\n", &FileObject->FileName); return TRUE; } Found = RtlLookupElementGenericTable (FileLock->LockInformation, &ToFind); if (!Found) { DPRINT("CheckForWrite(%wZ) => TRUE\n", &FileObject->FileName); return TRUE; } Result = Found->Exclusive.FileLock.Key == Key && Found->Exclusive.FileLock.ProcessId == EProcess; DPRINT("CheckForWrite(%wZ) => %s\n", &FileObject->FileName, Result ? "TRUE" : "FALSE"); return Result; } /* * @implemented */ NTSTATUS NTAPI FsRtlFastUnlockSingle(IN PFILE_LOCK FileLock, IN PFILE_OBJECT FileObject, IN PLARGE_INTEGER FileOffset, IN PLARGE_INTEGER Length, IN PEPROCESS Process, IN ULONG Key, IN PVOID Context OPTIONAL, IN BOOLEAN AlreadySynchronized) { BOOLEAN FoundShared = FALSE; PLIST_ENTRY SharedEntry; PLOCK_SHARED_RANGE SharedRange = NULL; COMBINED_LOCK_ELEMENT Find; PCOMBINED_LOCK_ELEMENT Entry; PIRP NextMatchingLockIrp; PLOCK_INFORMATION InternalInfo = FileLock->LockInformation; DPRINT("FsRtlFastUnlockSingle(%wZ, Offset %08x%08x (%d), Length %08x%08x (%d), Key %x)\n", &FileObject->FileName, FileOffset->HighPart, FileOffset->LowPart, (int)FileOffset->QuadPart, Length->HighPart, Length->LowPart, (int)Length->QuadPart, Key); // The region to unlock must correspond exactly to a previously locked region // -- msdn // But Windows 2003 doesn't assert on it and simply ignores that parameter // ASSERT(AlreadySynchronized); Find.Exclusive.FileLock.StartingByte = *FileOffset; Find.Exclusive.FileLock.EndingByte.QuadPart = FileOffset->QuadPart + Length->QuadPart; if (!InternalInfo) { DPRINT("File not previously locked (ever)\n"); return STATUS_RANGE_NOT_LOCKED; } Entry = RtlLookupElementGenericTable(&InternalInfo->RangeTable, &Find); if (!Entry) { DPRINT("Range not locked %wZ\n", &FileObject->FileName); return STATUS_RANGE_NOT_LOCKED; } DPRINT("Found lock entry: Exclusive %u %08x%08x:%08x%08x %wZ\n", Entry->Exclusive.FileLock.ExclusiveLock, Entry->Exclusive.FileLock.StartingByte.HighPart, Entry->Exclusive.FileLock.StartingByte.LowPart, Entry->Exclusive.FileLock.EndingByte.HighPart, Entry->Exclusive.FileLock.EndingByte.LowPart, &FileObject->FileName); if (Entry->Exclusive.FileLock.ExclusiveLock) { if (Entry->Exclusive.FileLock.Key != Key || Entry->Exclusive.FileLock.ProcessId != Process || Entry->Exclusive.FileLock.StartingByte.QuadPart != FileOffset->QuadPart || Entry->Exclusive.FileLock.EndingByte.QuadPart != FileOffset->QuadPart + Length->QuadPart) { DPRINT("Range not locked %wZ\n", &FileObject->FileName); return STATUS_RANGE_NOT_LOCKED; } RtlCopyMemory(&Find, Entry, sizeof(Find)); // Remove the old exclusive lock region RtlDeleteElementGenericTable(&InternalInfo->RangeTable, Entry); } else { DPRINT("Shared lock %wZ Start %08x%08x End %08x%08x\n", &FileObject->FileName, Entry->Exclusive.FileLock.StartingByte.HighPart, Entry->Exclusive.FileLock.StartingByte.LowPart, Entry->Exclusive.FileLock.EndingByte.HighPart, Entry->Exclusive.FileLock.EndingByte.LowPart); for (SharedEntry = InternalInfo->SharedLocks.Flink; SharedEntry != &InternalInfo->SharedLocks; SharedEntry = SharedEntry->Flink) { SharedRange = CONTAINING_RECORD(SharedEntry, LOCK_SHARED_RANGE, Entry); if (SharedRange->Start.QuadPart == FileOffset->QuadPart && SharedRange->End.QuadPart == FileOffset->QuadPart + Length->QuadPart && SharedRange->Key == Key && SharedRange->ProcessId == Process) { FoundShared = TRUE; DPRINT("Found shared element to delete %wZ Start %08x%08x End %08x%08x Key %x\n", &FileObject->FileName, SharedRange->Start.HighPart, SharedRange->Start.LowPart, SharedRange->End.HighPart, SharedRange->End.LowPart, SharedRange->Key); break; } } if (FoundShared) { /* Remove the found range from the shared range lists */ RemoveEntryList(&SharedRange->Entry); ExFreePoolWithTag(SharedRange, TAG_RANGE); /* We need to rebuild the list of shared ranges. */ DPRINT("Removing the lock entry %wZ (%08x%08x:%08x%08x)\n", &FileObject->FileName, Entry->Exclusive.FileLock.StartingByte.HighPart, Entry->Exclusive.FileLock.StartingByte.LowPart, Entry->Exclusive.FileLock.EndingByte.HighPart, Entry->Exclusive.FileLock.EndingByte.LowPart); /* Remember what was in there and remove it from the table */ Find = *Entry; RtlDeleteElementGenericTable(&InternalInfo->RangeTable, &Find); /* Put shared locks back in place */ for (SharedEntry = InternalInfo->SharedLocks.Flink; SharedEntry != &InternalInfo->SharedLocks; SharedEntry = SharedEntry->Flink) { COMBINED_LOCK_ELEMENT LockElement; SharedRange = CONTAINING_RECORD(SharedEntry, LOCK_SHARED_RANGE, Entry); LockElement.Exclusive.FileLock.FileObject = FileObject; LockElement.Exclusive.FileLock.StartingByte = SharedRange->Start; LockElement.Exclusive.FileLock.EndingByte = SharedRange->End; LockElement.Exclusive.FileLock.ProcessId = SharedRange->ProcessId; LockElement.Exclusive.FileLock.Key = SharedRange->Key; LockElement.Exclusive.FileLock.ExclusiveLock = FALSE; if (LockCompare(&InternalInfo->RangeTable, &Find, &LockElement) != GenericEqual) { DPRINT("Skipping range %08x%08x:%08x%08x\n", LockElement.Exclusive.FileLock.StartingByte.HighPart, LockElement.Exclusive.FileLock.StartingByte.LowPart, LockElement.Exclusive.FileLock.EndingByte.HighPart, LockElement.Exclusive.FileLock.EndingByte.LowPart); continue; } DPRINT("Re-creating range %08x%08x:%08x%08x\n", LockElement.Exclusive.FileLock.StartingByte.HighPart, LockElement.Exclusive.FileLock.StartingByte.LowPart, LockElement.Exclusive.FileLock.EndingByte.HighPart, LockElement.Exclusive.FileLock.EndingByte.LowPart); FsRtlpRebuildSharedLockRange(FileLock, InternalInfo, &LockElement); } } else { return STATUS_RANGE_NOT_LOCKED; } } #ifndef NDEBUG DPRINT("Lock still has:\n"); for (SharedEntry = InternalInfo->SharedLocks.Flink; SharedEntry != &InternalInfo->SharedLocks; SharedEntry = SharedEntry->Flink) { SharedRange = CONTAINING_RECORD(SharedEntry, LOCK_SHARED_RANGE, Entry); DPRINT("Shared element %wZ Offset %08x%08x Length %08x%08x Key %x\n", &FileObject->FileName, SharedRange->Start.HighPart, SharedRange->Start.LowPart, SharedRange->End.HighPart, SharedRange->End.LowPart, SharedRange->Key); } #endif // this is definitely the thing we want InternalInfo->Generation++; while ((NextMatchingLockIrp = IoCsqRemoveNextIrp(&InternalInfo->Csq, &Find))) { if (NextMatchingLockIrp->IoStatus.Information == InternalInfo->Generation) { // We've already looked at this one, meaning that we looped. // Put it back and exit. IoCsqInsertIrpEx (&InternalInfo->Csq, NextMatchingLockIrp, NULL, NULL); break; } // 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. DPRINT("Locking another IRP %p for %p %wZ\n", NextMatchingLockIrp, FileLock, &FileObject->FileName); FsRtlProcessFileLock(InternalInfo->BelongsTo, NextMatchingLockIrp, NULL); } DPRINT("Success %wZ\n", &FileObject->FileName); return STATUS_SUCCESS; } /* * @implemented */ NTSTATUS NTAPI FsRtlFastUnlockAll(IN PFILE_LOCK FileLock, IN PFILE_OBJECT FileObject, IN PEPROCESS Process, IN PVOID Context OPTIONAL) { PLIST_ENTRY ListEntry; PCOMBINED_LOCK_ELEMENT Entry; PLOCK_INFORMATION InternalInfo = FileLock->LockInformation; DPRINT("FsRtlFastUnlockAll(%wZ)\n", &FileObject->FileName); // XXX Synchronize somehow if (!FileLock->LockInformation) { DPRINT("Not locked %wZ\n", &FileObject->FileName); return STATUS_RANGE_NOT_LOCKED; // no locks } for (ListEntry = InternalInfo->SharedLocks.Flink; ListEntry != &InternalInfo->SharedLocks;) { LARGE_INTEGER Length; PLOCK_SHARED_RANGE Range = CONTAINING_RECORD(ListEntry, LOCK_SHARED_RANGE, Entry); Length.QuadPart = Range->End.QuadPart - Range->Start.QuadPart; ListEntry = ListEntry->Flink; if (Range->ProcessId != Process) continue; FsRtlFastUnlockSingle (FileLock, FileObject, &Range->Start, &Length, Range->ProcessId, Range->Key, Context, TRUE); } for (Entry = RtlEnumerateGenericTable(&InternalInfo->RangeTable, TRUE); Entry; Entry = RtlEnumerateGenericTable(&InternalInfo->RangeTable, 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); } DPRINT("Done %wZ\n", &FileObject->FileName); return STATUS_SUCCESS; } /* * @implemented */ NTSTATUS NTAPI FsRtlFastUnlockAllByKey(IN PFILE_LOCK FileLock, IN PFILE_OBJECT FileObject, IN PEPROCESS Process, IN ULONG Key, IN PVOID Context OPTIONAL) { PLIST_ENTRY ListEntry; PCOMBINED_LOCK_ELEMENT Entry; PLOCK_INFORMATION InternalInfo = FileLock->LockInformation; DPRINT("FsRtlFastUnlockAllByKey(%wZ,Key %x)\n", &FileObject->FileName, Key); // XXX Synchronize somehow if (!FileLock->LockInformation) return STATUS_RANGE_NOT_LOCKED; // no locks for (ListEntry = InternalInfo->SharedLocks.Flink; ListEntry != &InternalInfo->SharedLocks;) { PLOCK_SHARED_RANGE Range = CONTAINING_RECORD(ListEntry, LOCK_SHARED_RANGE, Entry); LARGE_INTEGER Length; Length.QuadPart = Range->End.QuadPart - Range->Start.QuadPart; ListEntry = ListEntry->Flink; if (Range->ProcessId != Process || Range->Key != Key) continue; FsRtlFastUnlockSingle (FileLock, FileObject, &Range->Start, &Length, Range->ProcessId, Range->Key, Context, TRUE); } for (Entry = RtlEnumerateGenericTable(&InternalInfo->RangeTable, TRUE); Entry; Entry = RtlEnumerateGenericTable(&InternalInfo->RangeTable, 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) { FsRtlFastUnlockSingle (FileLock, Entry->Exclusive.FileLock.FileObject, &Entry->Exclusive.FileLock.StartingByte, &Length, Entry->Exclusive.FileLock.ProcessId, Entry->Exclusive.FileLock.Key, Context, TRUE); } } return STATUS_SUCCESS; } /* * @implemented */ NTSTATUS NTAPI FsRtlProcessFileLock(IN PFILE_LOCK FileLock, IN PIRP Irp, IN PVOID Context OPTIONAL) { PIO_STACK_LOCATION IoStackLocation; NTSTATUS Status; IO_STATUS_BLOCK IoStatusBlock; /* Get the I/O Stack location */ IoStackLocation = IoGetCurrentIrpStackLocation(Irp); ASSERT(IoStackLocation->MajorFunction == IRP_MJ_LOCK_CONTROL); /* Clear the I/O status block and check what function this is */ IoStatusBlock.Information = 0; DPRINT("FsRtlProcessFileLock(%wZ, MinorFunction %x)\n", &IoStackLocation->FileObject->FileName, IoStackLocation->MinorFunction); switch(IoStackLocation->MinorFunction) { /* A lock */ case IRP_MN_LOCK: /* Call the private lock routine */ FsRtlPrivateLock(FileLock, IoStackLocation->FileObject, &IoStackLocation-> Parameters.LockControl.ByteOffset, IoStackLocation->Parameters.LockControl.Length, IoGetRequestorProcess(Irp), IoStackLocation->Parameters.LockControl.Key, IoStackLocation->Flags & SL_FAIL_IMMEDIATELY, IoStackLocation->Flags & SL_EXCLUSIVE_LOCK, &IoStatusBlock, Irp, Context, FALSE); return IoStatusBlock.Status; /* A single unlock */ case IRP_MN_UNLOCK_SINGLE: /* Call fast unlock */ IoStatusBlock.Status = FsRtlFastUnlockSingle(FileLock, IoStackLocation->FileObject, &IoStackLocation->Parameters.LockControl. ByteOffset, IoStackLocation->Parameters.LockControl. Length, IoGetRequestorProcess(Irp), IoStackLocation->Parameters.LockControl. Key, Context, FALSE); break; /* Total unlock */ case IRP_MN_UNLOCK_ALL: /* Do a fast unlock */ IoStatusBlock.Status = FsRtlFastUnlockAll(FileLock, IoStackLocation-> FileObject, IoGetRequestorProcess(Irp), Context); break; /* Unlock by key */ case IRP_MN_UNLOCK_ALL_BY_KEY: /* Do it */ IoStatusBlock.Status = FsRtlFastUnlockAllByKey(FileLock, IoStackLocation->FileObject, IoGetRequestorProcess(Irp), IoStackLocation->Parameters. LockControl.Key, Context); break; /* Invalid request */ default: /* Complete it */ FsRtlCompleteRequest(Irp, STATUS_INVALID_DEVICE_REQUEST); IoStatusBlock.Status = STATUS_INVALID_DEVICE_REQUEST; return STATUS_INVALID_DEVICE_REQUEST; } /* Return the status */ DPRINT("Lock IRP %p %x\n", Irp, IoStatusBlock.Status); FsRtlCompleteLockIrpReal (FileLock->CompleteLockIrpRoutine, Context, Irp, IoStatusBlock.Status, &Status, NULL); return IoStatusBlock.Status; } /* * @implemented */ VOID NTAPI FsRtlInitializeFileLock (IN PFILE_LOCK FileLock, IN PCOMPLETE_LOCK_IRP_ROUTINE CompleteLockIrpRoutine OPTIONAL, IN PUNLOCK_ROUTINE UnlockRoutine OPTIONAL) { /* Setup the lock */ RtlZeroMemory(FileLock, sizeof(*FileLock)); FileLock->FastIoIsQuestionable = FALSE; FileLock->CompleteLockIrpRoutine = CompleteLockIrpRoutine; FileLock->UnlockRoutine = UnlockRoutine; FileLock->LockInformation = NULL; } /* * @implemented */ VOID NTAPI FsRtlUninitializeFileLock(IN PFILE_LOCK FileLock) { if (FileLock->LockInformation) { PIRP Irp; PLOCK_INFORMATION InternalInfo = FileLock->LockInformation; PCOMBINED_LOCK_ELEMENT Entry; PLIST_ENTRY SharedEntry; PLOCK_SHARED_RANGE SharedRange; // MSDN: this completes any remaining lock IRPs for (SharedEntry = InternalInfo->SharedLocks.Flink; SharedEntry != &InternalInfo->SharedLocks;) { SharedRange = CONTAINING_RECORD(SharedEntry, LOCK_SHARED_RANGE, Entry); SharedEntry = SharedEntry->Flink; RemoveEntryList(&SharedRange->Entry); ExFreePoolWithTag(SharedRange, TAG_RANGE); } while ((Entry = RtlGetElementGenericTable(&InternalInfo->RangeTable, 0)) != NULL) { RtlDeleteElementGenericTable(&InternalInfo->RangeTable, Entry); } while ((Irp = IoCsqRemoveNextIrp(&InternalInfo->Csq, NULL)) != NULL) { FsRtlProcessFileLock(FileLock, Irp, NULL); } ExFreePoolWithTag(InternalInfo, TAG_FLOCK); FileLock->LockInformation = NULL; } } /* * @implemented */ PFILE_LOCK NTAPI FsRtlAllocateFileLock(IN PCOMPLETE_LOCK_IRP_ROUTINE CompleteLockIrpRoutine OPTIONAL, IN PUNLOCK_ROUTINE UnlockRoutine OPTIONAL) { PFILE_LOCK FileLock; /* Try to allocate it */ FileLock = ExAllocateFromPagedLookasideList(&FsRtlFileLockLookasideList); if (FileLock) { /* Initialize it */ FsRtlInitializeFileLock(FileLock, CompleteLockIrpRoutine, UnlockRoutine); } /* Return the lock */ return FileLock; } /* * @implemented */ VOID NTAPI FsRtlFreeFileLock(IN PFILE_LOCK FileLock) { /* Uninitialize and free the lock */ FsRtlUninitializeFileLock(FileLock); ExFreeToPagedLookasideList(&FsRtlFileLockLookasideList, FileLock); }