From: Pierre Schweitzer Date: Sat, 18 Nov 2017 17:23:57 +0000 (+0100) Subject: [NTOSKRNL] Implement FsRtlCheckOplock(), FsRtlCurrentBatchOplock(), FsRtlInitializeOp... X-Git-Tag: v0.4.7~49 X-Git-Url: https://git.reactos.org/?p=reactos.git;a=commitdiff_plain;h=d3d585395684b96df9782fe0af253f2c3e9cffc4;hp=89e83b2cf48c9bab7eec3670b69b72db675b0279 [NTOSKRNL] Implement FsRtlCheckOplock(), FsRtlCurrentBatchOplock(), FsRtlInitializeOplock(), FsRtlOplockFsctrl(), FsRtlOplockIsFastIoPossible(), FsRtlUninitializeOplock(). But also, implement FsRtlNotifyCompletion(), FsRtlCompletionRoutinePriv(), FsRtlRemoveAndCompleteWaitIrp(), FsRtlCancelWaitIrp(), FsRtlWaitOnIrp(), FsRtlOplockBreakNotify(), FsRtlRemoveAndCompleteIrp(), FsRtlCancelOplockIIIrp(), FsRtlAcknowledgeOplockBreak(), FsRtlOpBatchBreakClosePending(), FsRtlAllocateOplock(), FsRtlCancelExclusiveIrp(), FsRtlRequestExclusiveOplock(), FsRtlRequestOplockII(), FsRtlOplockCleanup(), FsRtlOplockBreakToNone(), FsRtlOplockBreakToII(). In short... Implement oplocks support in ReactOS. --- diff --git a/ntoskrnl/fsrtl/oplock.c b/ntoskrnl/fsrtl/oplock.c index ce31dfbab78..250f0fce3fa 100644 --- a/ntoskrnl/fsrtl/oplock.c +++ b/ntoskrnl/fsrtl/oplock.c @@ -3,7 +3,7 @@ * 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 ******************************************************************/ @@ -12,6 +12,1133 @@ #define NDEBUG #include +#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 **********************************************************/ /*++ @@ -48,14 +1175,181 @@ FsRtlCheckOplock(IN POPLOCK Oplock, IN POPLOCK_WAIT_COMPLETE_ROUTINE CompletionRoutine OPTIONAL, IN POPLOCK_FS_PREPOST_IRP PostIrpRoutine OPTIONAL) { - /* Unimplemented */ - UNIMPLEMENTED; + 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 * @@ -71,8 +1365,21 @@ BOOLEAN NTAPI FsRtlCurrentBatchOplock(IN POPLOCK Oplock) { - /* Unimplemented */ - UNIMPLEMENTED; + 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; } @@ -95,6 +1402,9 @@ NTAPI FsRtlInitializeOplock(IN OUT POPLOCK Oplock) { PAGED_CODE(); + + /* Nothing to do */ + DPRINT("FsRtlInitializeOplock(%p)\n", Oplock); } /*++ @@ -123,14 +1433,123 @@ FsRtlOplockFsctrl(IN POPLOCK Oplock, IN PIRP Irp, IN ULONG OpenCount) { - /* Unimplemented */ - UNIMPLEMENTED; - 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 * @@ -146,13 +1565,27 @@ BOOLEAN NTAPI FsRtlOplockIsFastIoPossible(IN POPLOCK Oplock) { - UNIMPLEMENTED; + 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 * @@ -168,6 +1601,103 @@ VOID NTAPI FsRtlUninitializeOplock(IN POPLOCK Oplock) { - UNIMPLEMENTED; + 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); }