* LICENSE: GPL - See COPYING in the top level directory
* FILE: ntoskrnl/fsrtl/oplock.c
* PURPOSE: Provides an Opportunistic Lock for file system drivers.
- * PROGRAMMERS: None.
+ * PROGRAMMERS: Pierre Schweitzer (pierre@reactos.org)
*/
/* INCLUDES ******************************************************************/
#define NDEBUG
#include <debug.h>
+#define NO_OPLOCK 0x1
+#define LEVEL_1_OPLOCK 0x2
+#define BATCH_OPLOCK 0x4
+#define FILTER_OPLOCK 0x8
+#define LEVEL_2_OPLOCK 0x10
+
+#define EXCLUSIVE_LOCK 0x40
+#define PENDING_LOCK 0x80
+
+#define BROKEN_TO_LEVEL_2 0x100
+#define BROKEN_TO_NONE 0x200
+#define BROKEN_TO_NONE_FROM_LEVEL_2 0x400
+#define BROKEN_TO_CLOSE_PENDING 0x800
+#define BROKEN_ANY (BROKEN_TO_LEVEL_2 | BROKEN_TO_NONE | BROKEN_TO_NONE_FROM_LEVEL_2 | BROKEN_TO_CLOSE_PENDING)
+
+#define TAG_OPLOCK 'orSF'
+
+typedef struct _INTERNAL_OPLOCK
+{
+ /* Level I IRP */
+ PIRP ExclusiveIrp;
+ /* Level I FILE_OBJECT */
+ PFILE_OBJECT FileObject;
+ /* Level II IRPs */
+ LIST_ENTRY SharedListHead;
+ /* IRPs waiting on level I */
+ LIST_ENTRY WaitListHead;
+ ULONG Flags;
+ PFAST_MUTEX IntLock;
+} INTERNAL_OPLOCK, *PINTERNAL_OPLOCK;
+
+typedef struct _WAIT_CONTEXT
+{
+ LIST_ENTRY WaitListEntry;
+ PIRP Irp;
+ POPLOCK_WAIT_COMPLETE_ROUTINE CompletionRoutine;
+ PVOID CompletionContext;
+ ULONG Reserved;
+ ULONG_PTR SavedInformation;
+} WAIT_CONTEXT, *PWAIT_CONTEXT;
+
+VOID
+NTAPI
+FsRtlNotifyCompletion(IN PVOID Context,
+ IN PIRP Irp)
+{
+ PAGED_CODE();
+
+ DPRINT("FsRtlNotifyCompletion(%p, %p)\n", Context, Irp);
+
+ /* Just complete the IRP */
+ return IoCompleteRequest(Irp, IO_DISK_INCREMENT);
+}
+
+VOID
+NTAPI
+FsRtlCompletionRoutinePriv(IN PVOID Context,
+ IN PIRP Irp)
+{
+ PKEVENT WaitEvent;
+
+ PAGED_CODE();
+
+ DPRINT("FsRtlCompletionRoutinePriv(%p, %p)\n", Context, Irp);
+
+ /* Set the event */
+ WaitEvent = (PKEVENT)Context;
+ KeSetEvent(WaitEvent, IO_NO_INCREMENT, FALSE);
+}
+
+VOID
+FsRtlRemoveAndCompleteWaitIrp(IN PWAIT_CONTEXT WaitCtx)
+{
+ PIRP Irp;
+
+ PAGED_CODE();
+
+ DPRINT("FsRtlRemoveAndCompleteWaitIrp(%p)\n", WaitCtx);
+
+ RemoveEntryList(&WaitCtx->WaitListEntry);
+ Irp = WaitCtx->Irp;
+
+ /* No cancel routine anymore */
+ IoAcquireCancelSpinLock(&Irp->CancelIrql);
+ IoSetCancelRoutine(Irp, NULL);
+ IoReleaseCancelSpinLock(Irp->CancelIrql);
+
+ /* Set the information */
+ Irp->IoStatus.Information = WaitCtx->SavedInformation;
+ /* Set the status according to the fact it got cancel or not */
+ Irp->IoStatus.Status = (Irp->Cancel ? STATUS_CANCELLED : STATUS_SUCCESS);
+
+ /* Call the completion routine */
+ WaitCtx->CompletionRoutine(WaitCtx->CompletionContext, Irp);
+
+ /* And get rid of the now useless wait context */
+ ExFreePoolWithTag(WaitCtx, TAG_OPLOCK);
+}
+
+VOID
+NTAPI
+FsRtlCancelWaitIrp(IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp)
+{
+ PINTERNAL_OPLOCK Oplock;
+ PLIST_ENTRY NextEntry;
+ PWAIT_CONTEXT WaitCtx;
+
+ DPRINT("FsRtlCancelWaitIrp(%p, %p)\n", DeviceObject, Irp);
+
+ /* Get the associated oplock */
+ Oplock = (PINTERNAL_OPLOCK)Irp->IoStatus.Information;
+
+ /* Remove the cancel routine (we're being called!) */
+ IoSetCancelRoutine(Irp, NULL);
+ /* And release the cancel spin lock (always locked when cancel routine is called) */
+ IoReleaseCancelSpinLock(Irp->CancelIrql);
+
+ /* Now, remove and complete any associated waiter */
+ ExAcquireFastMutex(Oplock->IntLock);
+ for (NextEntry = Oplock->WaitListHead.Flink;
+ NextEntry != &Oplock->WaitListHead;
+ NextEntry = NextEntry->Flink)
+ {
+ WaitCtx = CONTAINING_RECORD(NextEntry, WAIT_CONTEXT, WaitListEntry);
+
+ if (WaitCtx->Irp->Cancel)
+ {
+ FsRtlRemoveAndCompleteWaitIrp(WaitCtx);
+ }
+ }
+ ExReleaseFastMutex(Oplock->IntLock);
+}
+
+VOID
+FsRtlWaitOnIrp(IN PINTERNAL_OPLOCK Oplock,
+ IN PIRP Irp,
+ IN PVOID CompletionContext,
+ IN POPLOCK_WAIT_COMPLETE_ROUTINE CompletionRoutine,
+ IN POPLOCK_FS_PREPOST_IRP PostIrpRoutine,
+ IN PKEVENT WaitEvent)
+{
+ BOOLEAN Locked;
+ PWAIT_CONTEXT WaitCtx;
+
+ DPRINT("FsRtlWaitOnIrp(%p, %p, %p, %p, %p, %p)\n", Oplock, Irp, CompletionContext, CompletionRoutine, PostIrpRoutine, WaitEvent);
+
+ /* We must always be called with IntLock locked! */
+ Locked = TRUE;
+ /* Dirty check for above statement */
+ ASSERT(Oplock->IntLock->Owner == KeGetCurrentThread());
+
+ /* Allocate a wait context for the IRP */
+ WaitCtx = ExAllocatePoolWithTag(PagedPool | POOL_RAISE_IF_ALLOCATION_FAILURE, sizeof(WAIT_CONTEXT), TAG_OPLOCK);
+ WaitCtx->Irp = Irp;
+ WaitCtx->SavedInformation = Irp->IoStatus.Information;
+ /* If caller provided everything required, us it */
+ if (CompletionRoutine != NULL)
+ {
+ WaitCtx->CompletionRoutine = CompletionRoutine;
+ WaitCtx->CompletionContext = CompletionContext;
+ }
+ /* Otherwise, put ourselves */
+ else
+ {
+ WaitCtx->CompletionRoutine = FsRtlCompletionRoutinePriv;
+ WaitCtx->CompletionContext = WaitEvent;
+ KeInitializeEvent(WaitEvent, NotificationEvent, FALSE);
+ }
+
+ /* If we got a prepost routine, call it now! */
+ if (PostIrpRoutine != NULL)
+ {
+ PostIrpRoutine(CompletionContext, Irp);
+ }
+
+ Irp->IoStatus.Status = STATUS_SUCCESS;
+
+ /* Queue the IRP - it's OK, we're locked */
+ InsertHeadList(&Oplock->WaitListHead, &WaitCtx->WaitListEntry);
+
+ /* Set the oplock as information of the IRP (for the cancel routine)
+ * And lock the cancel routine lock for setting it
+ */
+ IoAcquireCancelSpinLock(&Irp->CancelIrql);
+ Irp->IoStatus.Information = (ULONG_PTR)Oplock;
+
+ /* If there's already a cancel routine
+ * Cancel the IRP
+ */
+ if (Irp->Cancel)
+ {
+ ExReleaseFastMutexUnsafe(Oplock->IntLock);
+ Locked = FALSE;
+
+ if (CompletionRoutine != NULL)
+ {
+ IoMarkIrpPending(Irp);
+ }
+ FsRtlCancelWaitIrp(NULL, Irp);
+ }
+ /* Otherwise, put ourselves as the cancel routine and start waiting */
+ else
+ {
+ IoSetCancelRoutine(Irp, FsRtlCancelWaitIrp);
+ IoReleaseCancelSpinLock(Irp->CancelIrql);
+ if (CompletionRoutine != NULL)
+ {
+ IoMarkIrpPending(Irp);
+ }
+ else
+ {
+ ExReleaseFastMutexUnsafe(Oplock->IntLock);
+ Locked = FALSE;
+ KeWaitForSingleObject(WaitEvent, Executive, KernelMode, FALSE, NULL);
+ }
+ }
+
+ /* If we didn't unlock yet, do it now */
+ if (Locked)
+ {
+ ExReleaseFastMutexUnsafe(Oplock->IntLock);
+ }
+}
+
+NTSTATUS
+FsRtlOplockBreakNotify(IN PINTERNAL_OPLOCK Oplock,
+ IN PIO_STACK_LOCATION Stack,
+ IN PIRP Irp)
+{
+ PAGED_CODE();
+
+ DPRINT("FsRtlOplockBreakNotify(%p, %p, %p)\n", Oplock, Stack, Irp);
+
+ /* No oplock, no break to notify */
+ if (Oplock == NULL)
+ {
+ Irp->IoStatus.Status = STATUS_SUCCESS;
+ IoCompleteRequest(Irp, IO_DISK_INCREMENT);
+ return STATUS_SUCCESS;
+ }
+
+ /* Notify by completing the IRP, unless we have broken to shared */
+ ExAcquireFastMutexUnsafe(Oplock->IntLock);
+ if (!BooleanFlagOn(Oplock->Flags, BROKEN_TO_LEVEL_2))
+ {
+ Irp->IoStatus.Status = STATUS_SUCCESS;
+ IoCompleteRequest(Irp, IO_DISK_INCREMENT);
+ ExReleaseFastMutexUnsafe(Oplock->IntLock);
+ return STATUS_SUCCESS;
+ }
+
+ /* If it's pending, just complete the IRP and get rid of the oplock */
+ if (BooleanFlagOn(Oplock->Flags, PENDING_LOCK))
+ {
+ Oplock->FileObject = NULL;
+ Oplock->Flags = NO_OPLOCK;
+ Irp->IoStatus.Status = STATUS_SUCCESS;
+ IoCompleteRequest(Irp, IO_DISK_INCREMENT);
+ ExReleaseFastMutexUnsafe(Oplock->IntLock);
+ return STATUS_SUCCESS;
+ }
+
+ /* Otherwise, wait on the IRP */
+ Irp->IoStatus.Status = STATUS_SUCCESS;
+ FsRtlWaitOnIrp(Oplock, Irp, NULL, FsRtlNotifyCompletion, NULL, NULL);
+ return STATUS_SUCCESS;
+}
+
+VOID
+FsRtlRemoveAndCompleteIrp(IN PIRP Irp)
+{
+ PIO_STACK_LOCATION Stack;
+
+ DPRINT("FsRtlRemoveAndCompleteIrp(%p)\n", Irp);
+
+ Stack = IoGetCurrentIrpStackLocation(Irp);
+
+ /* Remove our extra ref */
+ ObDereferenceObject(Stack->FileObject);
+
+ /* Remove our cancel routine */
+ IoAcquireCancelSpinLock(&Irp->CancelIrql);
+ IoSetCancelRoutine(Irp, NULL);
+ IoReleaseCancelSpinLock(Irp->CancelIrql);
+
+ /* Remove the IRP from the list it may be in (wait or shared) */
+ RemoveEntryList(&Irp->Tail.Overlay.ListEntry);
+
+ /* And complete! */
+ Irp->IoStatus.Information = FILE_OPLOCK_BROKEN_TO_NONE;
+ Irp->IoStatus.Status = (Irp->Cancel ? STATUS_CANCELLED : STATUS_SUCCESS);
+
+ IoCompleteRequest(Irp, IO_DISK_INCREMENT);
+}
+
+VOID
+NTAPI
+FsRtlCancelOplockIIIrp(IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp)
+{
+ PINTERNAL_OPLOCK Oplock;
+ PLIST_ENTRY NextEntry;
+ PIRP ListIrp;
+ BOOLEAN Removed;
+
+ DPRINT("FsRtlCancelOplockIIIrp(%p, %p)\n", DeviceObject, Irp);
+
+ /* Get the associated oplock */
+ Oplock = (PINTERNAL_OPLOCK)Irp->IoStatus.Information;
+
+ /* Remove the cancel routine (it's OK, we're the cancel routine! )*/
+ IoSetCancelRoutine(Irp, NULL);
+ IoReleaseCancelSpinLock(Irp->CancelIrql);
+
+ /* Nothing removed yet */
+ Removed = FALSE;
+ ExAcquireFastMutex(Oplock->IntLock);
+ /* Browse all the IRPs associated to the shared lock */
+ for (NextEntry = Oplock->SharedListHead.Flink;
+ NextEntry != &Oplock->SharedListHead;
+ NextEntry = NextEntry->Flink)
+ {
+ ListIrp = CONTAINING_RECORD(NextEntry, IRP, Tail.Overlay.ListEntry);
+
+ /* If canceled, remove it */
+ if (ListIrp->Cancel)
+ {
+ FsRtlRemoveAndCompleteIrp(ListIrp);
+ Removed = TRUE;
+ }
+ }
+
+ /* If no IRP left, the oplock is gone */
+ if (Removed && IsListEmpty(&Oplock->SharedListHead))
+ {
+ Oplock->Flags = NO_OPLOCK;
+ }
+ /* Don't forget to release the mutex */
+ ExReleaseFastMutex(Oplock->IntLock);
+}
+
+NTSTATUS
+FsRtlAcknowledgeOplockBreak(IN PINTERNAL_OPLOCK Oplock,
+ IN PIO_STACK_LOCATION Stack,
+ IN PIRP Irp,
+ IN BOOLEAN SwitchToLevel2)
+{
+ PLIST_ENTRY NextEntry;
+ PWAIT_CONTEXT WaitCtx;
+ BOOLEAN Deref;
+ BOOLEAN Locked;
+
+ DPRINT("FsRtlAcknowledgeOplockBreak(%p, %p, %p, %u)\n", Oplock, Stack, Irp, Unknown);
+
+ /* No oplock, nothing to acknowledge */
+ if (Oplock == NULL)
+ {
+ Irp->IoStatus.Status = STATUS_INVALID_OPLOCK_PROTOCOL;
+ IoCompleteRequest(Irp, IO_DISK_INCREMENT);
+ return STATUS_INVALID_OPLOCK_PROTOCOL;
+ }
+
+ /* Acquire oplock internal lock */
+ ExAcquireFastMutexUnsafe(Oplock->IntLock);
+ Locked = TRUE;
+ /* Does it match the file? */
+ if (Oplock->FileObject != Stack->FileObject)
+ {
+ Irp->IoStatus.Status = STATUS_INVALID_OPLOCK_PROTOCOL;
+ IoCompleteRequest(Irp, IO_DISK_INCREMENT);
+ ExReleaseFastMutexUnsafe(Oplock->IntLock);
+ return STATUS_INVALID_OPLOCK_PROTOCOL;
+ }
+
+ /* Assume we'll have to deref our extra ref (level I) */
+ Deref = TRUE;
+
+ /* If we got broken to level 2 and asked for a shared lock
+ * switch the oplock to shared
+ */
+ if (SwitchToLevel2 && BooleanFlagOn(Oplock->Flags, BROKEN_TO_LEVEL_2))
+ {
+ /* The IRP cannot be synchronous, we'll move it to the LEVEL_2 IRPs */
+ ASSERT(!IoIsOperationSynchronous(Irp));
+
+ /* Mark the IRP pending, and queue it for the shared IRPs */
+ IoMarkIrpPending(Irp);
+ Irp->IoStatus.Status = STATUS_SUCCESS;
+ InsertTailList(&Oplock->SharedListHead, &Irp->Tail.Overlay.ListEntry);
+
+ /* Don't deref, we're not done yet */
+ Deref = FALSE;
+ /* And mark we've got a shared lock */
+ Oplock->Flags = LEVEL_2_OPLOCK;
+ /* To find the lock back on cancel */
+ Irp->IoStatus.Information = (ULONG_PTR)Oplock;
+
+ /* Acquire the spinlock to set the cancel routine */
+ IoAcquireCancelSpinLock(&Irp->CancelIrql);
+ /* If IRP got canceled, call it immediately */
+ if (Irp->Cancel)
+ {
+ ExReleaseFastMutexUnsafe(Oplock->IntLock);
+ Locked = FALSE;
+ FsRtlCancelOplockIIIrp(NULL, Irp);
+ }
+ /* Otherwise, just set our cancel routine */
+ else
+ {
+ IoSetCancelRoutine(Irp, FsRtlCancelOplockIIIrp);
+ IoReleaseCancelSpinLock(Irp->CancelIrql);
+ }
+ }
+ /* If oplock got broken, remove it */
+ else if (BooleanFlagOn(Oplock->Flags, (BROKEN_TO_NONE | BROKEN_TO_LEVEL_2)))
+ {
+ Irp->IoStatus.Status = STATUS_SUCCESS;
+ IofCompleteRequest(Irp, IO_DISK_INCREMENT);
+ Oplock->Flags = NO_OPLOCK;
+ }
+ /* Same, but precise we got broken from none to shared */
+ else if (BooleanFlagOn(Oplock->Flags, BROKEN_TO_NONE_FROM_LEVEL_2))
+ {
+ Irp->IoStatus.Information = FILE_OPLOCK_BROKEN_TO_NONE;
+ Irp->IoStatus.Status = STATUS_SUCCESS;
+ IofCompleteRequest(Irp, IO_DISK_INCREMENT);
+ Oplock->Flags = NO_OPLOCK;
+ }
+
+ /* Now, complete any IRP waiting */
+ for (NextEntry = Oplock->WaitListHead.Flink;
+ NextEntry != &Oplock->WaitListHead;
+ NextEntry = NextEntry->Flink)
+ {
+ WaitCtx = CONTAINING_RECORD(NextEntry, WAIT_CONTEXT, WaitListEntry);
+ FsRtlRemoveAndCompleteWaitIrp(WaitCtx);
+ }
+
+ /* If we dropped oplock, remove our extra ref */
+ if (Deref)
+ {
+ ObfDereferenceObject(Oplock->FileObject);
+ }
+ /* And unset FO: no oplock left or shared */
+ Oplock->FileObject = NULL;
+
+ /* Don't leak the mutex! */
+ if (Locked)
+ {
+ ExReleaseFastMutexUnsafe(Oplock->IntLock);
+ }
+
+ return STATUS_SUCCESS;
+}
+
+NTSTATUS
+FsRtlOpBatchBreakClosePending(IN PINTERNAL_OPLOCK Oplock,
+ IN PIO_STACK_LOCATION Stack,
+ IN PIRP Irp)
+{
+ NTSTATUS Status;
+ PLIST_ENTRY NextEntry;
+ PWAIT_CONTEXT WaitCtx;
+
+ PAGED_CODE();
+
+ DPRINT("FsRtlOpBatchBreakClosePending(%p, %p, %p)\n", Oplock, Stack, Irp);
+
+ /* No oplock, that's not legit! */
+ if (Oplock == NULL)
+ {
+ Irp->IoStatus.Status = STATUS_INVALID_OPLOCK_PROTOCOL;
+ IoCompleteRequest(Irp, IO_DISK_INCREMENT);
+ return STATUS_INVALID_OPLOCK_PROTOCOL;
+ }
+
+ Status = STATUS_SUCCESS;
+ ExAcquireFastMutexUnsafe(Oplock->IntLock);
+
+ /* First of all, check if all conditions are met:
+ * Correct FO + broken oplock
+ */
+ if (Oplock->FileObject == Stack->FileObject && (BooleanFlagOn(Oplock->Flags, (BROKEN_TO_LEVEL_2 | BROKEN_TO_NONE | BROKEN_TO_NONE_FROM_LEVEL_2))))
+ {
+ /* If we have a pending or level 1 oplock... */
+ if (BooleanFlagOn(Oplock->Flags, (PENDING_LOCK | LEVEL_1_OPLOCK)))
+ {
+ /* Remove our extra ref from the FO */
+ if (Oplock->Flags & LEVEL_1_OPLOCK)
+ {
+ ObDereferenceObject(Oplock->FileObject);
+ }
+
+ /* And remove the oplock */
+ Oplock->Flags = NO_OPLOCK;
+ Oplock->FileObject = NULL;
+
+ /* Complete any waiting IRP */
+ for (NextEntry = Oplock->WaitListHead.Flink;
+ NextEntry != &Oplock->WaitListHead;
+ NextEntry = NextEntry->Flink)
+ {
+ WaitCtx = CONTAINING_RECORD(NextEntry, WAIT_CONTEXT, WaitListEntry);
+ FsRtlRemoveAndCompleteWaitIrp(WaitCtx);
+ }
+ }
+ /* Otherwise, mark the oplock as close pending */
+ else
+ {
+ ClearFlag(Oplock->Flags, BROKEN_ANY);
+ SetFlag(Oplock->Flags, BROKEN_TO_CLOSE_PENDING);
+ }
+ }
+ /* Oplock is in invalid state */
+ else
+ {
+ Status = STATUS_INVALID_OPLOCK_PROTOCOL;
+ }
+
+ /* And complete */
+ Irp->IoStatus.Status = Status;
+ IoCompleteRequest(Irp, IO_DISK_INCREMENT);
+ ExReleaseFastMutexUnsafe(Oplock->IntLock);
+
+ return Status;
+}
+
+PINTERNAL_OPLOCK
+FsRtlAllocateOplock(VOID)
+{
+ PINTERNAL_OPLOCK Oplock = NULL;
+
+ PAGED_CODE();
+
+ DPRINT("FsRtlAllocateOplock()\n");
+
+ _SEH2_TRY
+ {
+ /* Allocate and initialize the oplock */
+ Oplock = ExAllocatePoolWithTag(PagedPool | POOL_RAISE_IF_ALLOCATION_FAILURE | POOL_COLD_ALLOCATION, sizeof(INTERNAL_OPLOCK), TAG_OPLOCK);
+ RtlZeroMemory(Oplock, sizeof(INTERNAL_OPLOCK));
+ /* We allocate the fast mutex separately to have it non paged (while the rest of the oplock can be paged) */
+ Oplock->IntLock = ExAllocatePoolWithTag(NonPagedPool | POOL_RAISE_IF_ALLOCATION_FAILURE, sizeof(FAST_MUTEX), TAG_OPLOCK);
+ ExInitializeFastMutex(Oplock->IntLock);
+ /* Initialize the IRP list for level 2 oplock */
+ InitializeListHead(&Oplock->SharedListHead);
+ /* And for the wait IRPs */
+ InitializeListHead(&Oplock->WaitListHead);
+ Oplock->Flags = NO_OPLOCK;
+ }
+ _SEH2_FINALLY
+ {
+ /* In case of abnormal termination, it means either OPLOCK or FAST_MUTEX allocation failed */
+ if (_abnormal_termination())
+ {
+ /* That FAST_MUTEX, free OPLOCK */
+ if (Oplock != NULL)
+ {
+ ExFreePoolWithTag(Oplock, TAG_OPLOCK);
+ Oplock = NULL;
+ }
+ }
+ }
+ _SEH2_END;
+
+ return Oplock;
+}
+
+VOID
+NTAPI
+FsRtlCancelExclusiveIrp(IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp)
+{
+ PINTERNAL_OPLOCK IntOplock;
+ PLIST_ENTRY NextEntry;
+ PWAIT_CONTEXT WaitCtx;
+
+ DPRINT("FsRtlCancelExclusiveIrp(%p, %p)\n", DeviceObject, Irp);
+
+ /* Get the associated oplock */
+ IntOplock = (PINTERNAL_OPLOCK)Irp->IoStatus.Information;
+
+ /* Remove the cancel routine (us!) and release the cancel spinlock */
+ IoSetCancelRoutine(Irp, NULL);
+ IoReleaseCancelSpinLock(Irp->CancelIrql);
+
+ /* Acquire our internal FAST_MUTEX */
+ ExAcquireFastMutex(IntOplock->IntLock);
+ /* If we had an exclusive IRP */
+ if (IntOplock->ExclusiveIrp != NULL && IntOplock->ExclusiveIrp->Cancel)
+ {
+ /* Cancel it, and remove it from the oplock */
+ IntOplock->ExclusiveIrp->IoStatus.Status = STATUS_CANCELLED;
+ IoCompleteRequest(IntOplock->ExclusiveIrp, IO_DISK_INCREMENT);
+ IntOplock->ExclusiveIrp = NULL;
+
+ /* Dereference the fileobject and remove the oplock */
+ ObDereferenceObject(IntOplock->FileObject);
+ IntOplock->FileObject = NULL;
+ IntOplock->Flags = NO_OPLOCK;
+
+ /* And complete any waiting IRP */
+ for (NextEntry = IntOplock->WaitListHead.Flink;
+ NextEntry != &IntOplock->WaitListHead;
+ NextEntry = NextEntry->Flink)
+ {
+ WaitCtx = CONTAINING_RECORD(NextEntry, WAIT_CONTEXT, WaitListEntry);
+ FsRtlRemoveAndCompleteWaitIrp(WaitCtx);
+ }
+ }
+
+ /* Done! */
+ ExReleaseFastMutexUnsafe(IntOplock->IntLock);
+}
+
+NTSTATUS
+FsRtlRequestExclusiveOplock(IN POPLOCK Oplock,
+ IN PIO_STACK_LOCATION Stack,
+ IN PIRP Irp,
+ IN ULONG Flags)
+{
+ PINTERNAL_OPLOCK IntOplock;
+ PIRP ListIrp;
+ BOOLEAN Locked;
+ NTSTATUS Status;
+
+ DPRINT("FsRtlRequestExclusiveOplock(%p, %p, %p, %lu)\n", Oplock, Stack, Irp, Flags);
+
+ IntOplock = *Oplock;
+ Locked = FALSE;
+ Status = STATUS_SUCCESS;
+
+ /* Time to work! */
+ _SEH2_TRY
+ {
+ /* Was the oplock already allocated? If not, do it now! */
+ if (IntOplock == NULL)
+ {
+ *Oplock = FsRtlAllocateOplock();
+ IntOplock = *Oplock;
+ }
+
+ /* Acquire our internal lock */
+ ExAcquireFastMutexUnsafe(IntOplock->IntLock);
+ Locked = TRUE;
+
+ /* If we request exclusiveness, a filter or a pending oplock, grant it */
+ if (Flags == (EXCLUSIVE_LOCK | PENDING_LOCK | FILTER_OPLOCK))
+ {
+ /* Either no oplock, or pending */
+ ASSERT(BooleanFlagOn(IntOplock->Flags, (NO_OPLOCK | PENDING_LOCK)));
+ IntOplock->ExclusiveIrp = Irp;
+ IntOplock->FileObject = Stack->FileObject;
+ IntOplock->Flags = (EXCLUSIVE_LOCK | PENDING_LOCK | FILTER_OPLOCK);
+ }
+ else
+ {
+ /* Otherwise, shared or no effective oplock */
+ if (BooleanFlagOn(IntOplock->Flags, (LEVEL_2_OPLOCK | PENDING_LOCK | NO_OPLOCK)))
+ {
+ /* The shared IRPs list should contain a single entry! */
+ if (IntOplock->Flags == LEVEL_2_OPLOCK)
+ {
+ ListIrp = CONTAINING_RECORD(IntOplock->SharedListHead.Flink, IRP, Tail.Overlay.ListEntry);
+ ASSERT(IntOplock->SharedListHead.Flink == IntOplock->SharedListHead.Blink);
+ FsRtlRemoveAndCompleteIrp(ListIrp);
+ }
+
+ /* Set the exclusiveness */
+ IntOplock->ExclusiveIrp = Irp;
+ IntOplock->FileObject = Stack->FileObject;
+ IntOplock->Flags = Flags;
+
+ /* Mark the IRP pending and reference our file object */
+ IoMarkIrpPending(Irp);
+ ObReferenceObject(Stack->FileObject);
+ Irp->IoStatus.Information = (ULONG_PTR)IntOplock;
+
+ /* Now, set ourselves as cancel routine */
+ IoAcquireCancelSpinLock(&Irp->CancelIrql);
+ /* Unless IRP got canceled, then, just give up */
+ if (Irp->Cancel)
+ {
+ ExReleaseFastMutexUnsafe(IntOplock->IntLock);
+ Locked = FALSE;
+ FsRtlCancelExclusiveIrp(NULL, Irp);
+ Status = STATUS_CANCELLED;
+ }
+ else
+ {
+ IoSetCancelRoutine(Irp, FsRtlCancelExclusiveIrp);
+ IoReleaseCancelSpinLock(Irp->CancelIrql);
+ }
+ }
+ /* Cannot set exclusiveness, fail */
+ else
+ {
+ if (Irp != NULL)
+ {
+ Irp->IoStatus.Status = STATUS_OPLOCK_NOT_GRANTED;
+ IoCompleteRequest(Irp, IO_DISK_INCREMENT);
+ Status = STATUS_OPLOCK_NOT_GRANTED;
+ }
+ }
+ }
+ }
+ /* If locked, release */
+ _SEH2_FINALLY
+ {
+ if (Locked)
+ {
+ ExReleaseFastMutexUnsafe(IntOplock->IntLock);
+ }
+ }
+ _SEH2_END;
+
+ return Status;
+}
+
+NTSTATUS
+FsRtlRequestOplockII(IN POPLOCK Oplock,
+ IN PIO_STACK_LOCATION Stack,
+ IN PIRP Irp)
+{
+ BOOLEAN Locked;
+ NTSTATUS Status;
+ PINTERNAL_OPLOCK IntOplock;
+
+ DPRINT("FsRtlRequestOplockII(%p, %p, %p)\n", Oplock, Stack, Irp);
+
+ IntOplock = *Oplock;
+ Locked = FALSE;
+ Status = STATUS_SUCCESS;
+
+ _SEH2_TRY
+ {
+ /* No oplock yet? Allocate it */
+ if (IntOplock == NULL)
+ {
+ *Oplock = FsRtlAllocateOplock();
+ IntOplock = *Oplock;
+ }
+
+ /* Acquire the oplock */
+ ExAcquireFastMutexUnsafe(IntOplock->IntLock);
+ Locked = TRUE;
+
+ /* If already shared, or no oplock that's fine! */
+ if (BooleanFlagOn(IntOplock->Flags, (LEVEL_2_OPLOCK | NO_OPLOCK)))
+ {
+ IoMarkIrpPending(Irp);
+ /* Granted! */
+ Irp->IoStatus.Status = STATUS_SUCCESS;
+
+ /* Insert in the shared list */
+ InsertTailList(&IntOplock->SharedListHead, &Irp->Tail.Overlay.ListEntry);
+
+ /* Save the associated oplock */
+ Irp->IoStatus.Information = (ULONG_PTR)IntOplock;
+
+ /* The oplock is shared */
+ IntOplock->Flags = LEVEL_2_OPLOCK;
+
+ /* Reference the fileobject */
+ ObReferenceObject(Stack->FileObject);
+
+ /* Set our cancel routine, unless the IRP got canceled in-between */
+ IoAcquireCancelSpinLock(&Irp->CancelIrql);
+ if (Irp->Cancel)
+ {
+ ExReleaseFastMutexUnsafe(IntOplock->IntLock);
+ Locked = FALSE;
+ FsRtlCancelOplockIIIrp(NULL, Irp);
+ Status = STATUS_CANCELLED;
+ }
+ else
+ {
+ IoSetCancelRoutine(Irp, FsRtlCancelOplockIIIrp);
+ IoReleaseCancelSpinLock(Irp->CancelIrql);
+ }
+ }
+ /* Otherwise, just fail */
+ else
+ {
+ Irp->IoStatus.Status = STATUS_OPLOCK_NOT_GRANTED;
+ IoCompleteRequest(Irp, IO_DISK_INCREMENT);
+ Status = STATUS_OPLOCK_NOT_GRANTED;
+ }
+ }
+ _SEH2_FINALLY
+ {
+ if (Locked)
+ {
+ ExReleaseFastMutexUnsafe(IntOplock->IntLock);
+ }
+ }
+ _SEH2_END;
+
+ return Status;
+}
+
+VOID
+FsRtlOplockCleanup(IN PINTERNAL_OPLOCK Oplock,
+ IN PIO_STACK_LOCATION Stack)
+{
+ PIO_STACK_LOCATION ListStack;
+ PLIST_ENTRY NextEntry;
+ PIRP ListIrp;
+ PWAIT_CONTEXT WaitCtx;
+
+ DPRINT("FsRtlOplockCleanup(%p, %p)\n", Oplock, Stack);
+
+ ExAcquireFastMutexUnsafe(Oplock->IntLock);
+ /* oplock cleaning only makes sense if there's an oplock */
+ if (Oplock->Flags != NO_OPLOCK)
+ {
+ /* Shared lock */
+ if (Oplock->Flags == LEVEL_2_OPLOCK)
+ {
+ /* Complete any associated IRP */
+ for (NextEntry = Oplock->SharedListHead.Flink;
+ NextEntry != &Oplock->SharedListHead;
+ NextEntry = NextEntry->Flink)
+ {
+ ListIrp = CONTAINING_RECORD(NextEntry, IRP, Tail.Overlay.ListEntry);
+ ListStack = IoGetCurrentIrpStackLocation(ListIrp);
+
+ if (Stack->FileObject == ListStack->FileObject)
+ {
+ FsRtlRemoveAndCompleteIrp(ListIrp);
+ }
+ }
+
+ /* If, in the end, no IRP is left, then the lock is gone */
+ if (IsListEmpty(&Oplock->SharedListHead))
+ {
+ Oplock->Flags = NO_OPLOCK;
+ }
+ }
+ else
+ {
+ /* If we have matching file */
+ if (Oplock->FileObject == Stack->FileObject)
+ {
+ /* Oplock wasn't broken (still exclusive), easy case */
+ if (!BooleanFlagOn(Oplock->Flags, (BROKEN_ANY | PENDING_LOCK)))
+ {
+ /* Remove the cancel routine we set previously */
+ IoAcquireCancelSpinLock(&Oplock->ExclusiveIrp->CancelIrql);
+ IoSetCancelRoutine(Oplock->ExclusiveIrp, NULL);
+ IoReleaseCancelSpinLock(Oplock->ExclusiveIrp->CancelIrql);
+
+ /* And return the fact we broke the oplock to no oplock */
+ Oplock->ExclusiveIrp->IoStatus.Information = FILE_OPLOCK_BROKEN_TO_NONE;
+ Oplock->ExclusiveIrp->IoStatus.Status = STATUS_SUCCESS;
+
+ /* And complete! */
+ IoCompleteRequest(Oplock->ExclusiveIrp, IO_DISK_INCREMENT);
+ Oplock->ExclusiveIrp = NULL;
+ }
+
+ /* If no pending, we can safely dereference the file object */
+ if (!BooleanFlagOn(Oplock->Flags, PENDING_LOCK))
+ {
+ ObDereferenceObject(Oplock->FileObject);
+ }
+
+ /* Now, remove the oplock */
+ Oplock->FileObject = NULL;
+ Oplock->Flags = NO_OPLOCK;
+
+ /* And complete any waiting IRP */
+ for (NextEntry = Oplock->WaitListHead.Flink;
+ NextEntry != &Oplock->WaitListHead;
+ NextEntry = NextEntry->Flink)
+ {
+ WaitCtx = CONTAINING_RECORD(NextEntry, WAIT_CONTEXT, WaitListEntry);
+ FsRtlRemoveAndCompleteWaitIrp(WaitCtx);
+ }
+ }
+ }
+ }
+ ExReleaseFastMutexUnsafe(Oplock->IntLock);
+}
+
+NTSTATUS
+NTAPI
+FsRtlOplockBreakToNone(IN PINTERNAL_OPLOCK Oplock,
+ IN PIO_STACK_LOCATION Stack,
+ IN PIRP Irp,
+ IN PVOID Context,
+ IN POPLOCK_WAIT_COMPLETE_ROUTINE CompletionRoutine OPTIONAL,
+ IN POPLOCK_FS_PREPOST_IRP PostIrpRoutine OPTIONAL)
+{
+ PLIST_ENTRY NextEntry;
+ PWAIT_CONTEXT WaitCtx;
+ PIRP ListIrp;
+ KEVENT WaitEvent;
+
+ DPRINT("FsRtlOplockBreakToNone(%p, %p, %p, %p, %p, %p)\n", Oplock, Stack, Irp, Context, CompletionRoutine, PostIrpRoutine);
+
+ ExAcquireFastMutexUnsafe(Oplock->IntLock);
+
+ /* No oplock to break! */
+ if (Oplock->Flags == NO_OPLOCK)
+ {
+ ExReleaseFastMutexUnsafe(Oplock->IntLock);
+ return STATUS_SUCCESS;
+ }
+
+ /* Not broken yet, but set... Let's do it!
+ * Also, we won't break a shared oplock
+ */
+ if (!BooleanFlagOn(Oplock->Flags, (BROKEN_ANY | PENDING_LOCK | LEVEL_2_OPLOCK)))
+ {
+ /* Remove our cancel routine, no longer needed */
+ IoAcquireCancelSpinLock(&Oplock->ExclusiveIrp->CancelIrql);
+ IoSetCancelRoutine(Oplock->ExclusiveIrp, NULL);
+ IoReleaseCancelSpinLock(Oplock->ExclusiveIrp->CancelIrql);
+
+ /* If the IRP got canceled, we need to cleanup a bit */
+ if (Oplock->ExclusiveIrp->Cancel)
+ {
+ /* Return cancelation */
+ Oplock->ExclusiveIrp->IoStatus.Information = FILE_OPLOCK_BROKEN_TO_NONE;
+ Oplock->ExclusiveIrp->IoStatus.Status = STATUS_CANCELLED;
+ IoCompleteRequest(Oplock->ExclusiveIrp, IO_DISK_INCREMENT);
+
+ /* No oplock left */
+ Oplock->Flags = NO_OPLOCK;
+ Oplock->ExclusiveIrp = NULL;
+
+ /* No need for the FO anymore */
+ ObDereferenceObject(Oplock->FileObject);
+ Oplock->FileObject = NULL;
+
+ /* And complete any waiting IRP */
+ for (NextEntry = Oplock->WaitListHead.Flink;
+ NextEntry != &Oplock->WaitListHead;
+ NextEntry = NextEntry->Flink)
+ {
+ WaitCtx = CONTAINING_RECORD(NextEntry, WAIT_CONTEXT, WaitListEntry);
+ FsRtlRemoveAndCompleteWaitIrp(WaitCtx);
+ }
+
+ /* Done! */
+ ExReleaseFastMutexUnsafe(Oplock->IntLock);
+
+ return STATUS_SUCCESS;
+ }
+
+ /* Easier break, just complete :-) */
+ Oplock->ExclusiveIrp->IoStatus.Information = FILE_OPLOCK_BROKEN_TO_NONE;
+ Oplock->ExclusiveIrp->IoStatus.Status = STATUS_SUCCESS;
+ IoCompleteRequest(Oplock->ExclusiveIrp, IO_DISK_INCREMENT);
+
+ /* And remove our exclusive IRP */
+ Oplock->ExclusiveIrp = NULL;
+ SetFlag(Oplock->Flags, BROKEN_TO_NONE);
+ }
+ /* Shared lock */
+ else if (Oplock->Flags == LEVEL_2_OPLOCK)
+ {
+ /* Complete any IRP in the shared lock */
+ for (NextEntry = Oplock->SharedListHead.Flink;
+ NextEntry != &Oplock->SharedListHead;
+ NextEntry = NextEntry->Flink)
+ {
+ ListIrp = CONTAINING_RECORD(NextEntry, IRP, Tail.Overlay.ListEntry);
+ FsRtlRemoveAndCompleteIrp(ListIrp);
+ }
+
+ /* No lock left */
+ Oplock->Flags = NO_OPLOCK;
+
+ /* Done */
+ ExReleaseFastMutexUnsafe(Oplock->IntLock);
+ return STATUS_SUCCESS;
+ }
+ /* If it was broken to level 2, break it to none from level 2 */
+ else if (Oplock->Flags & BROKEN_TO_LEVEL_2)
+ {
+ ClearFlag(Oplock->Flags, BROKEN_TO_LEVEL_2);
+ SetFlag(Oplock->Flags, BROKEN_TO_NONE_FROM_LEVEL_2);
+ }
+ /* If it was pending, just drop the lock */
+ else if (BooleanFlagOn(Oplock->Flags, PENDING_LOCK))
+ {
+ Oplock->Flags = NO_OPLOCK;
+ Oplock->FileObject = NULL;
+
+ ExReleaseFastMutexUnsafe(Oplock->IntLock);
+ return STATUS_SUCCESS;
+ }
+
+ /* If that's ours, job done */
+ if (Oplock->FileObject == Stack->FileObject)
+ {
+ ExReleaseFastMutexUnsafe(Oplock->IntLock);
+ return STATUS_SUCCESS;
+ }
+
+ /* Otherwise, wait on the IRP */
+ if (Stack->MajorFunction != IRP_MJ_CREATE || !BooleanFlagOn(Stack->Parameters.Create.Options, FILE_COMPLETE_IF_OPLOCKED))
+ {
+ FsRtlWaitOnIrp(Oplock, Irp, Context, CompletionRoutine, PostIrpRoutine, &WaitEvent);
+
+ ExReleaseFastMutexUnsafe(Oplock->IntLock);
+
+ return STATUS_SUCCESS;
+ }
+ /* Done */
+ else
+ {
+ ExReleaseFastMutexUnsafe(Oplock->IntLock);
+ return STATUS_OPLOCK_BREAK_IN_PROGRESS;
+ }
+}
+
+NTSTATUS
+NTAPI
+FsRtlOplockBreakToII(IN PINTERNAL_OPLOCK Oplock,
+ IN PIO_STACK_LOCATION Stack,
+ IN PIRP Irp,
+ IN PVOID Context,
+ IN POPLOCK_WAIT_COMPLETE_ROUTINE CompletionRoutine OPTIONAL,
+ IN POPLOCK_FS_PREPOST_IRP PostIrpRoutine OPTIONAL)
+{
+ PLIST_ENTRY NextEntry;
+ PWAIT_CONTEXT WaitCtx;
+ KEVENT WaitEvent;
+
+ DPRINT("FsRtlOplockBreakToII(%p, %p, %p, %p, %p, %p)\n", Oplock, Stack, Irp, Context, CompletionRoutine, PostIrpRoutine);
+
+ ExAcquireFastMutexUnsafe(Oplock->IntLock);
+
+ /* If our lock, or if not exclusively locked, nothing to break! */
+ if (!BooleanFlagOn(Oplock->Flags, EXCLUSIVE_LOCK) || Oplock->FileObject == Stack->FileObject)
+ {
+ ExReleaseFastMutexUnsafe(Oplock->IntLock);
+ return STATUS_SUCCESS;
+ }
+
+ /* If already broken or not set yet */
+ if (BooleanFlagOn(Oplock->Flags, (BROKEN_ANY | PENDING_LOCK)))
+ {
+ /* Drop oplock if pending */
+ if (BooleanFlagOn(Oplock->Flags, PENDING_LOCK))
+ {
+ Oplock->Flags = NO_OPLOCK;
+ Oplock->FileObject = NULL;
+
+ ExReleaseFastMutexUnsafe(Oplock->IntLock);
+ return STATUS_SUCCESS;
+ }
+
+ }
+ /* To break! */
+ else
+ {
+ /* Drop the cancel routine of the exclusive IRP */
+ IoAcquireCancelSpinLock(&Oplock->ExclusiveIrp->CancelIrql);
+ IoSetCancelRoutine(Oplock->ExclusiveIrp, NULL);
+ IoReleaseCancelSpinLock(Oplock->ExclusiveIrp->CancelIrql);
+
+ /* If it was canceled in between, break to no oplock */
+ if (Oplock->ExclusiveIrp->Cancel)
+ {
+ /* Complete the IRP with cancellation */
+ Oplock->ExclusiveIrp->IoStatus.Information = FILE_OPLOCK_BROKEN_TO_NONE;
+ Oplock->ExclusiveIrp->IoStatus.Status = STATUS_CANCELLED;
+ IoCompleteRequest(Oplock->ExclusiveIrp, IO_DISK_INCREMENT);
+
+ /* And mark we have no longer lock */
+ Oplock->Flags = NO_OPLOCK;
+ Oplock->ExclusiveIrp = NULL;
+ ObDereferenceObject(Oplock->FileObject);
+ Oplock->FileObject = NULL;
+
+ /* Finally, complete any waiter */
+ for (NextEntry = Oplock->WaitListHead.Flink;
+ NextEntry != &Oplock->WaitListHead;
+ NextEntry = NextEntry->Flink)
+ {
+ WaitCtx = CONTAINING_RECORD(NextEntry, WAIT_CONTEXT, WaitListEntry);
+ FsRtlRemoveAndCompleteWaitIrp(WaitCtx);
+ }
+
+ ExReleaseFastMutexUnsafe(Oplock->IntLock);
+
+ return STATUS_SUCCESS;
+ }
+
+ /* It wasn't canceled, so break to shared unless we were alone, in that case we break to no lock! */
+ Oplock->ExclusiveIrp->IoStatus.Status = STATUS_SUCCESS;
+ if (BooleanFlagOn(Oplock->Flags, (BATCH_OPLOCK | LEVEL_1_OPLOCK)))
+ {
+ SetFlag(Oplock->Flags, BROKEN_TO_LEVEL_2);
+ Oplock->ExclusiveIrp->IoStatus.Information = FILE_OPLOCK_BROKEN_TO_LEVEL_2;
+ }
+ else
+ {
+ SetFlag(Oplock->Flags, BROKEN_TO_NONE);
+ Oplock->ExclusiveIrp->IoStatus.Information = FILE_OPLOCK_BROKEN_TO_NONE;
+ }
+ /* And complete */
+ IoCompleteRequest(Oplock->ExclusiveIrp, IO_DISK_INCREMENT);
+ Oplock->ExclusiveIrp = NULL;
+ }
+
+ /* Wait if required */
+ if (Stack->MajorFunction != IRP_MJ_CREATE || !BooleanFlagOn(Stack->Parameters.Create.Options, FILE_COMPLETE_IF_OPLOCKED))
+ {
+ FsRtlWaitOnIrp(Oplock, Irp, Context, CompletionRoutine, PostIrpRoutine, &WaitEvent);
+
+ ExReleaseFastMutexUnsafe(Oplock->IntLock);
+
+ return STATUS_SUCCESS;
+ }
+ else
+ {
+ ExReleaseFastMutexUnsafe(Oplock->IntLock);
+ return STATUS_OPLOCK_BREAK_IN_PROGRESS;
+ }
+}
+
/* PUBLIC FUNCTIONS **********************************************************/
/*++
IN POPLOCK_WAIT_COMPLETE_ROUTINE CompletionRoutine OPTIONAL,
IN POPLOCK_FS_PREPOST_IRP PostIrpRoutine OPTIONAL)
{
- /* Unimplemented */
- KeBugCheck(FILE_SYSTEM);
- return STATUS_NOT_IMPLEMENTED;
+ PINTERNAL_OPLOCK IntOplock;
+ PIO_STACK_LOCATION Stack;
+ ACCESS_MASK DesiredAccess;
+ FILE_INFORMATION_CLASS FileInfo;
+ ULONG CreateDisposition;
+
+#define BreakToIIIfRequired \
+ if (IntOplock->Flags != LEVEL_2_OPLOCK || IntOplock->FileObject != Stack->FileObject) \
+ return FsRtlOplockBreakToII(IntOplock, Stack, Irp, Context, CompletionRoutine, PostIrpRoutine)
+
+#define BreakToNoneIfRequired \
+ if (IntOplock->Flags == LEVEL_2_OPLOCK || IntOplock->FileObject != Stack->FileObject) \
+ return FsRtlOplockBreakToNone(IntOplock, Stack, Irp, Context, CompletionRoutine, PostIrpRoutine)
+
+ DPRINT("FsRtlCheckOplock(%p, %p, %p, %p, %p)\n", Oplock, Irp, Context, CompletionRoutine, PostIrpRoutine);
+
+ IntOplock = *Oplock;
+
+ /* No oplock, easy! */
+ if (IntOplock == NULL)
+ {
+ return STATUS_SUCCESS;
+ }
+
+ /* No sense on paging */
+ if (Irp->Flags & IRP_PAGING_IO)
+ {
+ return STATUS_SUCCESS;
+ }
+
+ /* No oplock, easy (bis!) */
+ if (IntOplock->Flags == NO_OPLOCK)
+ {
+ return STATUS_SUCCESS;
+ }
+
+ Stack = IoGetCurrentIrpStackLocation(Irp);
+
+ /* If cleanup, cleanup the associated oplock & return */
+ if (Stack->MajorFunction == IRP_MJ_CLEANUP)
+ {
+ FsRtlOplockCleanup(IntOplock, Stack);
+ return STATUS_SUCCESS;
+ }
+ else if (Stack->MajorFunction == IRP_MJ_LOCK_CONTROL)
+ {
+ /* OK for filter */
+ if (BooleanFlagOn(IntOplock->Flags, FILTER_OPLOCK))
+ {
+ return STATUS_SUCCESS;
+ }
+
+ /* Lock operation, we will have to break to no lock if shared or not us */
+ BreakToNoneIfRequired;
+
+ return STATUS_SUCCESS;
+ }
+ else if (Stack->MajorFunction == IRP_MJ_FILE_SYSTEM_CONTROL)
+ {
+ /* FSCTL should be safe, unless user wants a write FSCTL */
+ if (Stack->Parameters.FileSystemControl.FsControlCode != FSCTL_SET_ZERO_DATA)
+ {
+ return STATUS_SUCCESS;
+ }
+
+ /* We will have to break for write if shared or not us! */
+ BreakToNoneIfRequired;
+
+ return STATUS_SUCCESS;
+ }
+ else if (Stack->MajorFunction == IRP_MJ_WRITE)
+ {
+ /* Write operation, we will have to break if shared or not us */
+ BreakToNoneIfRequired;
+
+ return STATUS_SUCCESS;
+ }
+ else if (Stack->MajorFunction == IRP_MJ_READ)
+ {
+ /* If that's filter oplock, it's alright */
+ if (BooleanFlagOn(IntOplock->Flags, FILTER_OPLOCK))
+ {
+ return STATUS_SUCCESS;
+ }
+
+ /* Otherwise, we need to break to shared oplock */
+ BreakToIIIfRequired;
+
+ return STATUS_SUCCESS;
+ }
+ else if (Stack->MajorFunction == IRP_MJ_CREATE)
+ {
+ DesiredAccess = Stack->Parameters.Create.SecurityContext->DesiredAccess;
+
+ /* If that's just for reading, the oplock is fine */
+ if ((!(DesiredAccess & ~(SYNCHRONIZE | FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_READ_DATA)) && !(Stack->Parameters.Create.Options & FILE_RESERVE_OPFILTER))
+ || (BooleanFlagOn(IntOplock->Flags, FILTER_OPLOCK) && !(DesiredAccess & ~(SYNCHRONIZE | READ_CONTROL | FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_EXECUTE | FILE_READ_EA | FILE_WRITE_DATA)) && BooleanFlagOn(Stack->Parameters.Create.ShareAccess, FILE_SHARE_READ)))
+ {
+ return STATUS_SUCCESS;
+ }
+
+ /* Otherwise, check the disposition */
+ CreateDisposition = (Stack->Parameters.Create.Options >> 24) & 0x000000FF;
+ if (!CreateDisposition || CreateDisposition == FILE_OVERWRITE ||
+ CreateDisposition == FILE_OVERWRITE_IF ||
+ BooleanFlagOn(Stack->Parameters.Create.Options, FILE_RESERVE_OPFILTER))
+ {
+ /* Not us, we have to break the oplock! */
+ BreakToNoneIfRequired;
+
+ return STATUS_SUCCESS;
+ }
+
+ /* It's fine, we can have the oplock shared */
+ BreakToIIIfRequired;
+
+ return STATUS_SUCCESS;
+ }
+ else if (Stack->MajorFunction == IRP_MJ_FLUSH_BUFFERS)
+ {
+ /* We need to share the lock, if not done yet! */
+ BreakToIIIfRequired;
+
+ return STATUS_SUCCESS;
+ }
+ else if (Stack->MajorFunction == IRP_MJ_SET_INFORMATION)
+ {
+ /* Only deal with really specific classes */
+ FileInfo = Stack->Parameters.SetFile.FileInformationClass;
+ if (FileInfo == FileRenameInformation || FileInfo == FileLinkInformation ||
+ FileInfo == FileShortNameInformation)
+ {
+ /* No need to break */
+ if (!BooleanFlagOn(IntOplock->Flags, (FILTER_OPLOCK | BATCH_OPLOCK)))
+ {
+ return STATUS_SUCCESS;
+ }
+ /* Otherwise break to none */
+ else
+ {
+ BreakToNoneIfRequired;
+
+ return STATUS_SUCCESS;
+ }
+ }
+ else if (FileInfo == FileAllocationInformation)
+ {
+ BreakToNoneIfRequired;
+
+ return STATUS_SUCCESS;
+ }
+ else if (FileInfo == FileEndOfFileInformation)
+ {
+ /* Advance only, nothing to do */
+ if (Stack->Parameters.SetFile.AdvanceOnly)
+ {
+ return STATUS_SUCCESS;
+ }
+
+ /* Otherwise, attempt to break to none */
+ BreakToNoneIfRequired;
+
+ return STATUS_SUCCESS;
+ }
+ }
+
+#undef BreakToIIIfRequired
+#undef BreakToNoneIfRequired
+
+ return STATUS_SUCCESS;
}
/*++
* @name FsRtlCurrentBatchOplock
- * @unimplemented
+ * @implemented
*
* FILLME
*
NTAPI
FsRtlCurrentBatchOplock(IN POPLOCK Oplock)
{
- /* Unimplemented */
- KeBugCheck(FILE_SYSTEM);
+ PINTERNAL_OPLOCK IntOplock;
+
+ PAGED_CODE();
+
+ DPRINT("FsRtlCurrentBatchOplock(%p)\n", Oplock);
+
+ IntOplock = *Oplock;
+
+ /* Only return true if batch or filter oplock */
+ if (IntOplock != NULL &&
+ BooleanFlagOn(IntOplock->Flags, (FILTER_OPLOCK | BATCH_OPLOCK)))
+ {
+ return TRUE;
+ }
+
return FALSE;
}
/*++
* @name FsRtlInitializeOplock
- * @unimplemented
+ * @implemented
*
* FILLME
*
NTAPI
FsRtlInitializeOplock(IN OUT POPLOCK Oplock)
{
- /* Unimplemented */
- KeBugCheck(FILE_SYSTEM);
+ PAGED_CODE();
+
+ /* Nothing to do */
+ DPRINT("FsRtlInitializeOplock(%p)\n", Oplock);
}
/*++
IN PIRP Irp,
IN ULONG OpenCount)
{
- /* Unimplemented */
- KeBugCheck(FILE_SYSTEM);
- return STATUS_NOT_IMPLEMENTED;
+ PIO_STACK_LOCATION Stack;
+ PINTERNAL_OPLOCK IntOplock;
+
+ PAGED_CODE();
+
+ DPRINT("FsRtlOplockFsctrl(%p, %p, %lu)\n", Oplock, Irp, OpenCount);
+
+ IntOplock = *Oplock;
+ Stack = IoGetCurrentIrpStackLocation(Irp);
+ /* Make sure it's not called on create */
+ if (Stack->MajorFunction != IRP_MJ_CREATE)
+ {
+ switch (Stack->Parameters.FileSystemControl.FsControlCode)
+ {
+ case FSCTL_OPLOCK_BREAK_NOTIFY:
+ return FsRtlOplockBreakNotify(IntOplock, Stack, Irp);
+
+ case FSCTL_OPLOCK_BREAK_ACK_NO_2:
+ return FsRtlAcknowledgeOplockBreak(IntOplock, Stack, Irp, FALSE);
+
+ case FSCTL_OPBATCH_ACK_CLOSE_PENDING:
+ return FsRtlOpBatchBreakClosePending(IntOplock, Stack, Irp);
+
+ case FSCTL_REQUEST_OPLOCK_LEVEL_1:
+ /* We can only grant level 1 if synchronous, and only a single handle to it
+ * (plus, not a paging IO - obvious, and not cleanup done...)
+ */
+ if (OpenCount == 1 && !IoIsOperationSynchronous(Irp) &&
+ !BooleanFlagOn(Irp->Flags, IRP_SYNCHRONOUS_PAGING_IO) && !BooleanFlagOn(Stack->FileObject->Flags, FO_CLEANUP_COMPLETE))
+ {
+ return FsRtlRequestExclusiveOplock(Oplock, Stack, Irp, EXCLUSIVE_LOCK | LEVEL_1_OPLOCK);
+ }
+ /* Not matching, fail */
+ else
+ {
+ Irp->IoStatus.Status = STATUS_OPLOCK_NOT_GRANTED;
+ IoCompleteRequest(Irp, IO_DISK_INCREMENT);
+ return STATUS_OPLOCK_NOT_GRANTED;
+ }
+
+ case FSCTL_REQUEST_OPLOCK_LEVEL_2:
+ /* Shared can only be granted if no byte-range lock, and async operation
+ * (plus, not a paging IO - obvious, and not cleanup done...)
+ */
+ if (OpenCount == 0 && !IoIsOperationSynchronous(Irp) &&
+ !BooleanFlagOn(Irp->Flags, IRP_SYNCHRONOUS_PAGING_IO) && !BooleanFlagOn(Stack->FileObject->Flags, FO_CLEANUP_COMPLETE))
+ {
+ return FsRtlRequestOplockII(Oplock, Stack, Irp);
+ }
+ /* Not matching, fail */
+ else
+ {
+ Irp->IoStatus.Status = STATUS_OPLOCK_NOT_GRANTED;
+ IoCompleteRequest(Irp, IO_DISK_INCREMENT);
+ return STATUS_OPLOCK_NOT_GRANTED;
+ }
+
+ case FSCTL_OPLOCK_BREAK_ACKNOWLEDGE:
+ return FsRtlAcknowledgeOplockBreak(IntOplock, Stack, Irp, TRUE);
+
+ case FSCTL_REQUEST_BATCH_OPLOCK:
+ /* Batch oplock can only be granted if there's a byte-range lock and async operation
+ * (plus, not a paging IO - obvious, and not cleanup done...)
+ */
+ if (OpenCount == 1 && !IoIsOperationSynchronous(Irp) &&
+ !BooleanFlagOn(Irp->Flags, IRP_SYNCHRONOUS_PAGING_IO) && !BooleanFlagOn(Stack->FileObject->Flags, FO_CLEANUP_COMPLETE))
+ {
+ return FsRtlRequestExclusiveOplock(Oplock, Stack, Irp, EXCLUSIVE_LOCK | BATCH_OPLOCK);
+ }
+ /* Not matching, fail */
+ else
+ {
+ Irp->IoStatus.Status = STATUS_OPLOCK_NOT_GRANTED;
+ IoCompleteRequest(Irp, IO_DISK_INCREMENT);
+ return STATUS_OPLOCK_NOT_GRANTED;
+ }
+
+ case FSCTL_REQUEST_FILTER_OPLOCK:
+ /* Filter oplock can only be granted if there's a byte-range lock and async operation
+ * (plus, not a paging IO - obvious, and not cleanup done...)
+ */
+ if (OpenCount == 1 && !IoIsOperationSynchronous(Irp) &&
+ !BooleanFlagOn(Irp->Flags, IRP_SYNCHRONOUS_PAGING_IO) && !BooleanFlagOn(Stack->FileObject->Flags, FO_CLEANUP_COMPLETE))
+ {
+ return FsRtlRequestExclusiveOplock(Oplock, Stack, Irp, EXCLUSIVE_LOCK | FILTER_OPLOCK);
+ }
+ /* Not matching, fail */
+ else
+ {
+ Irp->IoStatus.Status = STATUS_OPLOCK_NOT_GRANTED;
+ IoCompleteRequest(Irp, IO_DISK_INCREMENT);
+ return STATUS_OPLOCK_NOT_GRANTED;
+ }
+
+ default:
+ Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
+ IoCompleteRequest(Irp, IO_DISK_INCREMENT);
+ return STATUS_INVALID_PARAMETER;
+ }
+ }
+
+ /* That's a create operation! Only grant exclusive if there's a single user handle opened
+ * and we're only performing reading operations.
+ */
+ if (OpenCount == 1 &&
+ !(Stack->Parameters.Create.SecurityContext->DesiredAccess & ~(FILE_READ_ATTRIBUTES | FILE_READ_DATA)) &&
+ (Stack->Parameters.Create.ShareAccess & FILE_SHARE_VALID_FLAGS) == FILE_SHARE_VALID_FLAGS)
+ {
+ return FsRtlRequestExclusiveOplock(Oplock, Stack, NULL, EXCLUSIVE_LOCK | PENDING_LOCK | FILTER_OPLOCK);
+ }
+
+ return STATUS_OPLOCK_NOT_GRANTED;
}
/*++
* @name FsRtlOplockIsFastIoPossible
- * @unimplemented
+ * @implemented
*
* FILLME
*
NTAPI
FsRtlOplockIsFastIoPossible(IN POPLOCK Oplock)
{
- /* Unimplemented */
- KeBugCheck(FILE_SYSTEM);
- return FALSE;
+ PINTERNAL_OPLOCK IntOplock;
+
+ PAGED_CODE();
+
+ DPRINT("FsRtlOplockIsFastIoPossible(%p)\n", Oplock);
+
+ IntOplock = *Oplock;
+
+ /* If there's a shared oplock or if it was used for write operation, deny FastIO */
+ if (IntOplock != NULL &&
+ BooleanFlagOn(IntOplock->Flags, (BROKEN_ANY | LEVEL_2_OPLOCK)))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
}
/*++
* @name FsRtlUninitializeOplock
- * @unimplemented
+ * @implemented
*
* FILLME
*
NTAPI
FsRtlUninitializeOplock(IN POPLOCK Oplock)
{
- /* Unimplemented */
- KeBugCheck(FILE_SYSTEM);
+ PINTERNAL_OPLOCK IntOplock;
+ PLIST_ENTRY NextEntry;
+ PWAIT_CONTEXT WaitCtx;
+ PIRP Irp;
+ PIO_STACK_LOCATION Stack;
+
+ DPRINT("FsRtlUninitializeOplock(%p)\n", Oplock);
+
+ IntOplock = *Oplock;
+
+ /* No oplock, nothing to do */
+ if (IntOplock == NULL)
+ {
+ return;
+ }
+
+ /* Caller won't have the oplock anymore */
+ *Oplock = NULL;
+
+ _SEH2_TRY
+ {
+ ExAcquireFastMutexUnsafe(IntOplock->IntLock);
+
+ /* If we had IRPs waiting for the lock, complete them */
+ for (NextEntry = IntOplock->WaitListHead.Flink;
+ NextEntry != &IntOplock->WaitListHead;
+ NextEntry = NextEntry->Flink)
+ {
+ WaitCtx = CONTAINING_RECORD(NextEntry, WAIT_CONTEXT, WaitListEntry);
+ Irp = WaitCtx->Irp;
+
+ RemoveEntryList(&WaitCtx->WaitListEntry);
+ /* Remove the cancel routine */
+ IoAcquireCancelSpinLock(&Irp->CancelIrql);
+ IoSetCancelRoutine(Irp, NULL);
+ IoReleaseCancelSpinLock(Irp->CancelIrql);
+
+ /* And complete */
+ Irp->IoStatus.Information = 0;
+ WaitCtx->CompletionRoutine(WaitCtx->CompletionContext, WaitCtx->Irp);
+
+ ExFreePoolWithTag(WaitCtx, TAG_OPLOCK);
+ }
+
+ /* If we had shared IRPs (LEVEL_2), complete them */
+ for (NextEntry = IntOplock->SharedListHead.Flink;
+ NextEntry != &IntOplock->SharedListHead;
+ NextEntry = NextEntry->Flink)
+ {
+ Irp = CONTAINING_RECORD(NextEntry, IRP, Tail.Overlay.ListEntry);
+
+ RemoveEntryList(&Irp->Tail.Overlay.ListEntry);
+
+ /* Remvoe the cancel routine */
+ IoAcquireCancelSpinLock(&Irp->CancelIrql);
+ IoSetCancelRoutine(Irp, NULL);
+ IoReleaseCancelSpinLock(Irp->CancelIrql);
+
+ /* Dereference the file object */
+ Stack = IoGetCurrentIrpStackLocation(Irp);
+ ObDereferenceObject(Stack->FileObject);
+
+ /* And complete */
+ Irp->IoStatus.Information = FILE_OPLOCK_BROKEN_TO_NONE;
+ Irp->IoStatus.Status = STATUS_SUCCESS;
+ IoCompleteRequest(Irp, IO_DISK_INCREMENT);
+ }
+
+ /* If we have an exclusive IRP, complete it */
+ Irp = IntOplock->ExclusiveIrp;
+ if (Irp != NULL)
+ {
+ /* Remvoe the cancel routine */
+ IoAcquireCancelSpinLock(&Irp->CancelIrql);
+ IoSetCancelRoutine(Irp, NULL);
+ IoReleaseCancelSpinLock(Irp->CancelIrql);
+
+ /* And complete */
+ Irp->IoStatus.Information = FILE_OPLOCK_BROKEN_TO_NONE;
+ Irp->IoStatus.Status = STATUS_SUCCESS;
+ IoCompleteRequest(Irp, IO_DISK_INCREMENT);
+ IntOplock->ExclusiveIrp = NULL;
+
+ /* If still referenced, dereference */
+ if (IntOplock->FileObject != NULL)
+ {
+ ObDereferenceObject(IntOplock->FileObject);
+ }
+ }
+ }
+ _SEH2_FINALLY
+ {
+ ExReleaseFastMutexUnsafe(IntOplock->IntLock);
+ }
+ _SEH2_END;
+
+ ExFreePoolWithTag(IntOplock->IntLock, TAG_OPLOCK);
+ ExFreePoolWithTag(IntOplock, TAG_OPLOCK);
}