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); }