/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*-
 * ex: set softtabstop=4 tabstop=8 expandtab shiftwidth=4: *
 * Editor Settings: expandtabs and use 4 spaces for indentation */

/*
 * Copyright Likewise Software
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.  You should have received a copy of the GNU General
 * Public License along with this program.  If not, see
 * <http://www.gnu.org/licenses/>.
 *
 * LIKEWISE SOFTWARE MAKES THIS SOFTWARE AVAILABLE UNDER OTHER LICENSING
 * TERMS AS WELL.  IF YOU HAVE ENTERED INTO A SEPARATE LICENSE AGREEMENT
 * WITH LIKEWISE SOFTWARE, THEN YOU MAY ELECT TO USE THE SOFTWARE UNDER THE
 * TERMS OF THAT SOFTWARE LICENSE AGREEMENT INSTEAD OF THE TERMS OF THE GNU
 * GENERAL PUBLIC LICENSE, NOTWITHSTANDING THE ABOVE NOTICE.  IF YOU
 * HAVE QUESTIONS, OR WISH TO REQUEST A COPY OF THE ALTERNATE LICENSING
 * TERMS OFFERED BY LIKEWISE SOFTWARE, PLEASE CONTACT LIKEWISE SOFTWARE AT
 * license@likewisesoftware.com
 */

/*
 * Copyright (C) Likewise Software. All rights reserved.
 *
 * Module Name:
 *
 *        oplock.c
 *
 * Abstract:
 *
 *        Likewise IO (LWIO) - SRV
 *
 *        Protocols API - SMBV2
 *
 *        Opportunistic locks
 *
 * Authors: Sriram Nambakam (snambakam@likewise.com)
 *
 */

#include "includes.h"

static
NTSTATUS
SrvAcknowledgeOplockBreak_SMB_V2(
    PSRV_OPLOCK_STATE_SMB_V2 pOplockState,
    PUCHAR                   pucNewOplockLevel,
    BOOLEAN                  bFileIsClosed
    );

static
NTSTATUS
SrvAcknowledgeLeaseBreak_SMB_V2(
    IN OUT PSRV_OPLOCK_STATE_SMB_V2 pOplockState,
    IN ULONG LeaseState
    );

static
NTSTATUS
SrvBuildOplockBreakNotification_SMB_V2(
    PSRV_EXEC_CONTEXT        pExecContext,
    PSRV_OPLOCK_STATE_SMB_V2 pOplockState,
    UCHAR                    ucOplockLevel
    );

static
NTSTATUS
SrvBuildLeaseBreakNotification_SMB_V2(
    IN OUT PSRV_EXEC_CONTEXT pExecContext,
    IN PSRV_OPLOCK_STATE_SMB_V2 pOplockState,
    IN IO_LEASE_STATE CurrentLeaseState,
    IN IO_LEASE_STATE NewLeaseState
    );

static
NTSTATUS
SrvBuildOplockBreakResponse_SMB_V2(
    PSRV_EXEC_CONTEXT        pExecContext,
    PSRV_OPLOCK_STATE_SMB_V2 pOplockState,
    UCHAR                    ucOplockLevel
    );

static
NTSTATUS
SrvBuildLeaseBreakResponse_SMB_V2(
    IN OUT PSRV_EXEC_CONTEXT        pExecContext,
    IN PSRV_OPLOCK_STATE_SMB_V2 pOplockState,
    IN PSMB2_LEASE_BREAK_ACK_HEADER RequestLeaseBreakHeader
    );

static
VOID
SrvOplockExpiredCB_SMB_V2(
    PSRV_TIMER_REQUEST pTimerRequest,
    PVOID              pUserData
    );

static
NTSTATUS
SrvEnqueueOplockAckTask_SMB_V2(
    PSRV_OPLOCK_STATE_SMB_V2 pOplockState
    );

static
VOID
SrvCancelOplock_SMB_V2(
    HANDLE hOplock
    );

static
VOID
SrvFreeOplock_SMB_V2(
    HANDLE hOplock
    );

static
NTSTATUS
SrvOplockFindOpenFileId(
    IN PSRV_LEASE_RECORD pLeaseRecord,
    OUT PSMB2_FID pFileId
    );

static
VOID
SrvOplockAsyncCB_SMB_V2(
    PVOID pContext
    );

static
NTSTATUS
SrvReparentOplock_SMB_V2(
    HANDLE hOplock,
    PLWIO_SRV_FILE_2 pSrvFile,
    PSRV_DURABLE_ENTRY pDurable
    );

static
NTSTATUS
SrvBuildOplockExecContext_SMB_V2(
    PSRV_OPLOCK_STATE_SMB_V2 pOplockState,
    USHORT                   usOplockAction,
    BOOLEAN                  bInLock,
    PSRV_EXEC_CONTEXT*       ppExecContext
    );


////////////////////////////////////////////////////////////////////////

NTSTATUS
SrvBuildOplockState_SMB_V2(
    PLWIO_SRV_FILE_2          pFile,
    PSRV_OPLOCK_STATE_SMB_V2* ppOplockState
    )
{
    NTSTATUS                 ntStatus     = STATUS_SUCCESS;
    PSRV_OPLOCK_STATE_SMB_V2 pOplockState = NULL;

    ntStatus = SrvAllocateMemory(
                    sizeof(SRV_OPLOCK_STATE_SMB_V2),
                    (PVOID*)&pOplockState);
    BAIL_ON_NT_STATUS(ntStatus);

    pOplockState->Oplock.refCount           = 1;
    pOplockState->Oplock.pfnCancelOplock    = SrvCancelOplock_SMB_V2;
    pOplockState->Oplock.pfnFreeOplock      = SrvFreeOplock_SMB_V2;
    pOplockState->Oplock.pfnReparentOplock  = SrvReparentOplock_SMB_V2;
    pOplockState->Oplock.pfnGetOplockLevel  = SrvOplockHandleGetLevel_SMB_V2;
    LwListInit(&pOplockState->Oplock.LeaseRecordEntry);

    pthread_mutex_init(&pOplockState->mutex, NULL);
    pOplockState->pMutex = &pOplockState->mutex;

    pOplockState->pFile = SrvFile2Acquire(pFile);

    *ppOplockState = pOplockState;

cleanup:

    return ntStatus;

error:

    *ppOplockState = NULL;

    if (pOplockState)
    {
        SrvFreeOplock_SMB_V2(pOplockState);
    }

    goto cleanup;
}

static
NTSTATUS
SrvAcquireOplockFile_SMB_V2(
    PSRV_OPLOCK_STATE_SMB_V2 pOplockState,
    BOOLEAN bInLock,
    PLWIO_SRV_FILE_2* ppFile
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    BOOLEAN bTookLock = FALSE;
    PLWIO_SRV_FILE_2 pFile = NULL;

    if (!bInLock)
    {
        LWIO_LOCK_MUTEX(bTookLock, &pOplockState->mutex);
    }

    if (!pOplockState->pFile)
    {
        status = STATUS_USER_SESSION_DELETED;
        BAIL_ON_NT_STATUS(status);
    }

    pFile = SrvFile2Acquire(pOplockState->pFile);

cleanup:

    *ppFile = pFile;

    LWIO_UNLOCK_MUTEX(bTookLock, &pOplockState->mutex);

    return status;

error:

    goto cleanup;
}

NTSTATUS
SrvProcessOplock_SMB_V2(
    PSRV_EXEC_CONTEXT pExecContext
    )
{
    NTSTATUS                   ntStatus     = 0;
    PLWIO_SRV_CONNECTION       pConnection  = pExecContext->pConnection;
    PSRV_EXEC_CONTEXT_SMB_V2   pCtxSmb2     = pExecContext->pProtocolContext;
    ULONG                      iMsg         = pCtxSmb2->iMsg;
    PSRV_MESSAGE_SMB_V2        pSmbRequest  = &pCtxSmb2->pRequests[iMsg];
    PSMB2_OPLOCK_BREAK_HEADER  pRequestHeader = NULL; // Do not free
    PLWIO_SRV_SESSION_2        pSession      = NULL;
    PLWIO_SRV_TREE_2           pTree         = NULL;
    PLWIO_SRV_FILE_2           pFile         = NULL;
    PSRV_OPLOCK_STATE_SMB_V2   pOplockState  = NULL;
    UCHAR                      ucOplockLevel = SMB2_OPLOCK_LEVEL_NONE;
    BOOLEAN                    bFileLocked   = FALSE;
    BOOLEAN isAckNeeded = FALSE;

    ntStatus = SrvConnection2FindSession_SMB_V2(
                            pCtxSmb2,
                            pConnection,
                            pSmbRequest->pHeader->ullSessionId,
                            &pSession);
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = SrvSession2FindTree_SMB_V2(
                    pCtxSmb2,
                    pSession,
                    pSmbRequest->pHeader->ulTid,
                    &pTree);
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = SMB2UnmarshalOplockBreakRequest(
                    pSmbRequest,
                    &pRequestHeader);
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = SrvTree2FindFile_SMB_V2(
                    pCtxSmb2,
                    pTree,
                    &pRequestHeader->fid,
                    &pFile);
    BAIL_ON_NT_STATUS(ntStatus);

    switch (pRequestHeader->ulReserved)
    {
        case LW_SMB2_OPLOCK_ACTION_SEND_BREAK:

            LWIO_LOCK_RWMUTEX_SHARED(bFileLocked, &pFile->mutex);

            pOplockState = pFile->hOplock;
            if (pOplockState)
            {
                SrvAcquireOplock(pOplockState);
            }

            LWIO_UNLOCK_RWMUTEX(bFileLocked, &pFile->mutex);

            if (!pOplockState)
            {
                ntStatus = STATUS_INTERNAL_ERROR;
                BAIL_ON_NT_STATUS(ntStatus);
            }

            switch (pOplockState->oplockBuffer_out.OplockBreakResult)
            {
                case IO_OPLOCK_BROKEN_TO_NONE:
                    ucOplockLevel = SMB2_OPLOCK_LEVEL_BROKEN_TO_NONE;
                    ntStatus = SrvBuildOplockBreakNotification_SMB_V2(
                                   pExecContext,
                                   pOplockState,
                                   ucOplockLevel);
                    BAIL_ON_NT_STATUS(ntStatus);
                    break;

                case IO_OPLOCK_BROKEN_TO_LEVEL_2:
                    ucOplockLevel = SMB2_OPLOCK_LEVEL_BROKEN_TO_LEVEL_II;
                    ntStatus = SrvBuildOplockBreakNotification_SMB_V2(
                                   pExecContext,
                                   pOplockState,
                                   ucOplockLevel);
                    BAIL_ON_NT_STATUS(ntStatus);
                    break;

                case IO_OPLOCK_BROKEN_LEASE:
                    ntStatus = SrvBuildLeaseBreakNotification_SMB_V2(
                                   pExecContext,
                                   pOplockState,
                                   pOplockState->oplockBuffer_out.CurrentLeaseState,
                                   pOplockState->oplockBuffer_out.NewLeaseState);
                    BAIL_ON_NT_STATUS(ntStatus);
                    break;

                default:

                    ntStatus = STATUS_INTERNAL_ERROR;
                    BAIL_ON_NT_STATUS(ntStatus);

                    break;
            }

            pOplockState->bBreakRequestSent = TRUE;

            LWIO_LOCK_RWMUTEX_SHARED(bFileLocked, &pFile->mutex);

            switch (pOplockState->CurrentOplockLevel)    // current oplock level
            {
                case SMB2_OPLOCK_LEVEL_LEASE:
                    if (IsSetFlag(
                            pOplockState->oplockBuffer_out.LeaseBreakFlags,
                            IO_LEASE_FLAG_ACK_REQUIRED))
                    {
                        isAckNeeded = TRUE;
                    }
                    break;

                case SMB2_OPLOCK_LEVEL_I:
                case SMB2_OPLOCK_LEVEL_BATCH:
                    isAckNeeded = TRUE;
                    break;

                case SMB2_OPLOCK_LEVEL_II:
                case SMB2_OPLOCK_LEVEL_NONE:
                    // no ack needed
                    break;

                default:
                    ntStatus = STATUS_INTERNAL_ERROR;
                    break;
            }
            LWIO_UNLOCK_RWMUTEX(bFileLocked, &pFile->mutex);

            BAIL_ON_NT_STATUS(ntStatus);

            if (isAckNeeded)
            {
                LONG64 llExpiry = 0LL;

                ntStatus = WireGetCurrentNTTime(&llExpiry);
                BAIL_ON_NT_STATUS(ntStatus);

                /* configured timeout will be in milliseconds */
                llExpiry +=
                    (SrvConfigGetOplockTimeout_SMB_V2() *
                     WIRE_FACTOR_MILLISECS_TO_HUNDREDS_OF_NANOSECS);

                SrvAcquireOplock(pOplockState);

                ntStatus = SrvTimerPostRequest(
                               llExpiry,
                               pOplockState,
                               &SrvOplockExpiredCB_SMB_V2,
                               &pOplockState->pTimerRequest);
                if (ntStatus != STATUS_SUCCESS)
                {
                    SrvReleaseOplock(pOplockState);
                }
                BAIL_ON_NT_STATUS(ntStatus);
            }
            else
            {
                /* Level2 can only break to none. No Ack needed.
                   Remove any remaining oplock state */

                SrvFile2ResetOplock(pFile);
                ntStatus = STATUS_SUCCESS;
            }

            break;

        case LW_SMB2_OPLOCK_ACTION_PROCESS_ACK:

            LWIO_LOCK_RWMUTEX_SHARED(bFileLocked, &pFile->mutex);

            pOplockState = pFile->hOplock;
            if (pOplockState)
            {
                SrvAcquireOplock(pOplockState);
            }
            LWIO_UNLOCK_RWMUTEX(bFileLocked, &pFile->mutex);

            if (pOplockState)
            {
                if (pOplockState->pLeaseContext == NULL)
                {
                    ntStatus = SrvAcknowledgeOplockBreak_SMB_V2(
                                   pOplockState,
                                   NULL,
                                   FALSE);
                }
                else
                {
                    ntStatus = SrvAcknowledgeLeaseBreak_SMB_V2(
                                   pOplockState,
                                   SMB2_LEASE_NONE);
                }
                BAIL_ON_NT_STATUS(ntStatus);
            }

            break;

        default:

            ntStatus = STATUS_INTERNAL_ERROR;
            BAIL_ON_NT_STATUS(ntStatus);
    }

cleanup:

    if (pOplockState)
    {
        SrvReleaseOplock(pOplockState);
    }

    if (pFile)
    {
        SrvFile2Release(pFile);
    }

    if (pTree)
    {
        SrvTree2Release(pTree);
    }

    if (pSession)
    {
        SrvSession2Release(pSession);
    }

    return ntStatus;

error:

    goto cleanup;
}

NTSTATUS
SrvProcessOplockBreak_SMB_V2(
    PSRV_EXEC_CONTEXT pExecContext
    )
{
    NTSTATUS                   ntStatus     = 0;
    PLWIO_SRV_CONNECTION       pConnection  = pExecContext->pConnection;
    PSRV_EXEC_CONTEXT_SMB_V2   pCtxSmb2     = pExecContext->pProtocolContext;
    ULONG                      iMsg         = pCtxSmb2->iMsg;
    PSRV_MESSAGE_SMB_V2        pSmbRequest  = &pCtxSmb2->pRequests[iMsg];
    PSMB2_OPLOCK_BREAK_HEADER pOplockRequestHeader = NULL; // Do not free
    PSMB2_LEASE_BREAK_ACK_HEADER pLeaseRequestHeader = NULL; // Do not free
    PLWIO_SRV_SESSION_2        pSession      = NULL;
    PLWIO_SRV_TREE_2           pTree         = NULL;
    PLWIO_SRV_FILE_2           pFile         = NULL;
    PSRV_OPLOCK_STATE_SMB_V2   pOplockState  = NULL;
    PSRV_LEASE_RECORD          pLeaseRecord  = NULL;
    BOOLEAN                    bFileLocked   = FALSE;
    USHORT headerSize = 0;
    SMB2_FID fileIdentifier = { 0 };
    PSMB2_FID pFid = NULL;
    BOOLEAN bInLock = FALSE;

    ntStatus = SrvConnection2FindSession_SMB_V2(
                            pCtxSmb2,
                            pConnection,
                            pSmbRequest->pHeader->ullSessionId,
                            &pSession);
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = SrvSession2FindTree_SMB_V2(
                    pCtxSmb2,
                    pSession,
                    pSmbRequest->pHeader->ulTid,
                    &pTree);
    BAIL_ON_NT_STATUS(ntStatus);

    headerSize = LW_LTOH16(*(pSmbRequest->pBuffer + pSmbRequest->ulHeaderSize));

    switch (headerSize)
    {
        case sizeof(SMB2_OPLOCK_BREAK_HEADER):
            ntStatus = SMB2UnmarshalOplockBreakRequest(
                           pSmbRequest,
                           &pOplockRequestHeader);
            BAIL_ON_NT_STATUS(ntStatus);

            pFid = &pOplockRequestHeader->fid;
            break;

        case sizeof(SMB2_LEASE_BREAK_ACK_HEADER):
            ntStatus = SMB2UnmarshalLeaseBreakAckRequest(
                            pSmbRequest,
                            &pLeaseRequestHeader);
            BAIL_ON_NT_STATUS(ntStatus);

            ntStatus = SrvLeaseRecordFind(
                            pConnection,
                            &pLeaseRequestHeader->LeaseKey,
                            &pLeaseRecord);
            BAIL_ON_NT_STATUS(ntStatus);

            ntStatus = SrvOplockFindOpenFileId(
                            pLeaseRecord,
                            &fileIdentifier);
            BAIL_ON_NT_STATUS(ntStatus);

            pFid = &fileIdentifier;
            break;

        default:
            ntStatus = STATUS_INVALID_NETWORK_RESPONSE;
            BAIL_ON_NT_STATUS(ntStatus);
            break;
    }

    ntStatus = SrvTree2FindFile_SMB_V2(pCtxSmb2, pTree, pFid, &pFile);
    BAIL_ON_NT_STATUS(ntStatus);

    LWIO_LOCK_RWMUTEX_SHARED(bFileLocked, &pFile->mutex);

    pOplockState = pFile->hOplock;
    if (pOplockState)
    {
        SrvAcquireOplock(pOplockState);
    }
    LWIO_UNLOCK_RWMUTEX(bFileLocked, &pFile->mutex);

    if (pOplockState == NULL)
    {
        // Client is trying to ack an oplock that is no longer
        // valid (due to rundown or it never had one).

        ntStatus = STATUS_INVALID_OPLOCK_PROTOCOL;
        BAIL_ON_NT_STATUS(ntStatus);
    }

    // Release the timer on the break ack now

    LWIO_LOCK_MUTEX(bInLock, &pOplockState->mutex);

    if (pOplockState->pTimerRequest)
    {
        ntStatus = SrvTimerCancelRequest(
                        pOplockState->pTimerRequest,
                        NULL);
        if (ntStatus != STATUS_SUCCESS)
        {
            // If the timer is already firing, it is too late.
            // TODO: Decide if we need to synchronize with timer
            // acking the break.
            ntStatus = STATUS_INVALID_DEVICE_STATE;
        }
        BAIL_ON_NT_STATUS(ntStatus);

        SrvTimerRelease(&pOplockState->pTimerRequest);
        SrvReleaseOplock(pOplockState);
    }

    LWIO_UNLOCK_MUTEX(bInLock, &pOplockState->mutex);

    if (pOplockRequestHeader != NULL)
    {
        // Legacy oplock break
        ntStatus = SrvAcknowledgeOplockBreak_SMB_V2(
                       pOplockState,
                       &pOplockRequestHeader->ucOplockLevel,
                       FALSE);
        BAIL_ON_NT_STATUS(ntStatus);

        ntStatus = SrvBuildOplockBreakResponse_SMB_V2(
                       pExecContext,
                       pOplockState,
                       pOplockRequestHeader->ucOplockLevel);
        BAIL_ON_NT_STATUS(ntStatus);
    }
    else
    {
        IO_LEASE_STATE ioLeaseState = SrvLeaseMapSmb2ToIoLeaseState(
                                            pLeaseRequestHeader->LeaseState);

        // SMB 2.1 Lease break
        ntStatus = SrvAcknowledgeLeaseBreak_SMB_V2(
                       pOplockState,
                       pLeaseRequestHeader->LeaseState);
        BAIL_ON_NT_STATUS(ntStatus);

        pLeaseRecord->LeaseState = ioLeaseState;

        ntStatus = SrvBuildLeaseBreakResponse_SMB_V2(
                       pExecContext,
                       pOplockState,
                       pLeaseRequestHeader);
        BAIL_ON_NT_STATUS(ntStatus);
    }

error:

    LWIO_UNLOCK_MUTEX(bInLock, &pOplockState->mutex);

    SrvLeaseRecordRelease(&pLeaseRecord);

    if (pOplockState)
    {
        SrvReleaseOplock(pOplockState);
    }

    if (pFile)
    {
        SrvFile2Release(pFile);
    }

    if (pTree)
    {
        SrvTree2Release(pTree);
    }

    if (pSession)
    {
        SrvSession2Release(pSession);
    }

    return ntStatus;
}

static
NTSTATUS
SrvAcknowledgeOplockBreak_SMB_V2(
    PSRV_OPLOCK_STATE_SMB_V2 pOplockState,
    PUCHAR                   pucNewOplockLevel,
    BOOLEAN                  bFileIsClosed
    )
{
    NTSTATUS            ntStatus      = STATUS_SUCCESS;
    UCHAR               ucOplockLevel = SMB2_OPLOCK_LEVEL_NONE;
    PLWIO_SRV_FILE_2    pFile         = NULL;

    /* Lock is not held at this point */
    ntStatus = SrvAcquireOplockFile_SMB_V2(pOplockState, FALSE, &pFile);
    BAIL_ON_NT_STATUS(ntStatus);

    /* Clear flag indicating we are waiting an ACK from the client */

    pOplockState->bBreakInProgress = FALSE;
    pOplockState->bBreakRequestSent = FALSE;

    switch (pOplockState->oplockBuffer_out.OplockBreakResult)
    {
        case IO_OPLOCK_BROKEN_TO_NONE:

            ucOplockLevel = SMB2_OPLOCK_LEVEL_NONE;

            break;

        case IO_OPLOCK_BROKEN_TO_LEVEL_2:

            ucOplockLevel = SMB2_OPLOCK_LEVEL_II;

            break;

        default:

            ntStatus = STATUS_INTERNAL_ERROR;
            BAIL_ON_NT_STATUS(ntStatus);

            break;
    }

    if (bFileIsClosed)
    {
        pOplockState->oplockBuffer_ack.Response = IO_OPLOCK_BREAK_CLOSE_PENDING;
    }
    else
    {
        if (pucNewOplockLevel &&
            (*pucNewOplockLevel == SMB2_OPLOCK_LEVEL_NONE) &&
            (ucOplockLevel == SMB2_OPLOCK_LEVEL_II))
        {
            pOplockState->oplockBuffer_ack.Response =
                                                IO_OPLOCK_BREAK_ACK_NO_LEVEL_2;
        }
        else
        {
            pOplockState->oplockBuffer_ack.Response =
                                                IO_OPLOCK_BREAK_ACKNOWLEDGE;
        }
    }

    SrvPrepareOplockStateAsync_SMB_V2(pOplockState);

    ntStatus = IoFsControlFile(
                    pFile->hFile,
                    pOplockState->pAcb,
                    &pOplockState->ioStatusBlock,
                    IO_FSCTL_OPLOCK_BREAK_ACK,
                    &pOplockState->oplockBuffer_ack,
                    sizeof(pOplockState->oplockBuffer_ack),
                    &pOplockState->oplockBuffer_out,
                    sizeof(pOplockState->oplockBuffer_out));

    switch (ntStatus)
    {
        case STATUS_PENDING:
            SrvOplockSetLevel_SMB_V2(pFile, ucOplockLevel, SMB2_LEASE_NONE);

            ntStatus = STATUS_SUCCESS;

            break;

        default:

            SrvReleaseOplockStateAsync_SMB_V2(pOplockState); // completed sync

            BAIL_ON_NT_STATUS(ntStatus);

            break;
    }

cleanup:

    if (pFile)
    {
        SrvFile2Release(pFile);
    }

    return ntStatus;

error:

    goto cleanup;
}

static
NTSTATUS
SrvAcknowledgeLeaseBreak_SMB_V2(
    IN OUT PSRV_OPLOCK_STATE_SMB_V2 pOplockState,
    IN ULONG LeaseState
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    PLWIO_SRV_FILE_2 pFile = NULL;

    /* Lock is not held at this point */
    status = SrvAcquireOplockFile_SMB_V2(pOplockState, FALSE, &pFile);
    BAIL_ON_NT_STATUS(status);

    /* Clear flag indicating we are waiting an ACK from the client */

    pOplockState->bBreakRequestSent = FALSE;

    LwRtlZeroMemory(
        &pOplockState->oplockBuffer_in,
        sizeof(pOplockState->oplockBuffer_in));

    LwRtlZeroMemory(
        &pOplockState->oplockBuffer_out,
        sizeof(pOplockState->oplockBuffer_out));

    pOplockState->oplockBuffer_in.OplockRequestType = IO_OPLOCK_REQUEST_LEASE;
    pOplockState->oplockBuffer_in.LeaseFlags = IO_LEASE_FLAG_ACK;
    pOplockState->oplockBuffer_in.RequestLeaseState = SrvLeaseMapSmb2ToIoLeaseState(LeaseState);

    SrvPrepareOplockStateAsync_SMB_V2(pOplockState);

    status = IoFsControlFile(
                    pFile->hFile,
                    pOplockState->pAcb,
                    &pOplockState->ioStatusBlock,
                    IO_FSCTL_OPLOCK_REQUEST,
                    &pOplockState->oplockBuffer_in,
                    sizeof(pOplockState->oplockBuffer_in),
                    &pOplockState->oplockBuffer_out,
                    sizeof(pOplockState->oplockBuffer_out));

    switch (status)
    {
        case STATUS_PENDING:

            SrvOplockSetLevel_SMB_V2(pFile, SMB2_OPLOCK_LEVEL_LEASE, LeaseState);

            status = STATUS_SUCCESS;

            break;

        default:

            SrvReleaseOplockStateAsync_SMB_V2(pOplockState); // completed sync

            BAIL_ON_NT_STATUS(status);

            break;
    }

error:

    if (pFile)
    {
        SrvFile2Release(pFile);
    }

    return status;
}

static
NTSTATUS
SrvBuildOplockBreakNotification_SMB_V2(
    PSRV_EXEC_CONTEXT        pExecContext,
    PSRV_OPLOCK_STATE_SMB_V2 pOplockState,
    UCHAR                    ucOplockLevel
    )
{
    NTSTATUS                   ntStatus      = STATUS_SUCCESS;
    PSRV_EXEC_CONTEXT_SMB_V2   pCtxSmb2      = pExecContext->pProtocolContext;
    ULONG                      iMsg          = pCtxSmb2->iMsg;
    PSRV_MESSAGE_SMB_V2        pSmbRequest   = &pCtxSmb2->pRequests[iMsg];
    PSRV_MESSAGE_SMB_V2        pSmbResponse  = &pCtxSmb2->pResponses[iMsg];
    PSMB2_OPLOCK_BREAK_HEADER  pOplockBreakHeader = NULL; // Do not free
    PBYTE pOutBuffer       = pSmbResponse->pBuffer;
    ULONG ulBytesAvailable = pSmbResponse->ulBytesAvailable;
    ULONG ulOffset         = 0;
    ULONG ulTotalBytesUsed = 0;
    PLWIO_SRV_FILE_2 pFile = NULL;

    // Lock is not held at this point
    ntStatus = SrvAcquireOplockFile_SMB_V2(pOplockState, FALSE, &pFile);
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = SMB2MarshalHeader(
                        pOutBuffer,
                        ulOffset,
                        ulBytesAvailable,
                        COM2_BREAK,
                        pSmbRequest->CreditCharge,
                        pExecContext->usCreditsGranted,
                        0L,   /* Process Id */
                        pSmbRequest->pHeader->ullCommandSequence,
                        0L,   /* Tree Id    */
                        0LL,  /* Session Id */
                        0LL,  /* Async Id   */
                        STATUS_SUCCESS,
                        TRUE,
                        LwIsSetFlag(
                            pSmbRequest->pHeader->ulFlags,
                            SMB2_FLAGS_RELATED_OPERATION),
                        &pSmbResponse->pHeader,
                        &pSmbResponse->ulHeaderSize);
    BAIL_ON_NT_STATUS(ntStatus);

    pOutBuffer       += pSmbResponse->ulHeaderSize;
    ulOffset         += pSmbResponse->ulHeaderSize;
    ulBytesAvailable -= pSmbResponse->ulHeaderSize;
    ulTotalBytesUsed += pSmbResponse->ulHeaderSize;

    if (ulBytesAvailable < sizeof(SMB2_OPLOCK_BREAK_HEADER))
    {
        ntStatus = STATUS_INVALID_NETWORK_RESPONSE;
        BAIL_ON_NT_STATUS(ntStatus);
    }

    pOplockBreakHeader = (PSMB2_OPLOCK_BREAK_HEADER)pOutBuffer;

    pOplockBreakHeader->usLength = COM2_BREAK_OPLOCK_NOTIFY_STRUCT_SIZE;
    pOplockBreakHeader->fid      = pFile->fid;
    pOplockBreakHeader->ucOplockLevel = ucOplockLevel;


    // pOutBuffer       += sizeof(SMB2_OPLOCK_BREAK_HEADER);
    // ulBytesUsed       = sizeof(SMB2_OPLOCK_BREAK_HEADER);
    // ulOffset         += sizeof(SMB2_OPLOCK_BREAK_HEADER);
    ulBytesAvailable -= sizeof(SMB2_OPLOCK_BREAK_HEADER);
    ulTotalBytesUsed += sizeof(SMB2_OPLOCK_BREAK_HEADER);

    pSmbResponse->ulMessageSize = ulTotalBytesUsed;

cleanup:

    if (pFile)
    {
        SrvFile2Release(pFile);
    }

    return ntStatus;

error:

    if (ulTotalBytesUsed)
    {
        pSmbResponse->pHeader      = NULL;
        pSmbResponse->ulHeaderSize = 0;
        memset(pSmbResponse->pBuffer, 0, ulTotalBytesUsed);
    }

    pSmbResponse->ulMessageSize = 0;

    goto cleanup;
}

static
NTSTATUS
SrvBuildLeaseBreakNotification_SMB_V2(
    IN OUT PSRV_EXEC_CONTEXT pExecContext,
    IN PSRV_OPLOCK_STATE_SMB_V2 pOplockState,
    IN IO_LEASE_STATE CurrentLeaseState,
    IN IO_LEASE_STATE NewLeaseState
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PSRV_EXEC_CONTEXT_SMB_V2 pCtxSmb2 = pExecContext->pProtocolContext;
    ULONG iMsg = pCtxSmb2->iMsg;
    PSRV_MESSAGE_SMB_V2 pSmbRequest = &pCtxSmb2->pRequests[iMsg];
    PSRV_MESSAGE_SMB_V2 pSmbResponse = &pCtxSmb2->pResponses[iMsg];
    PSMB2_LEASE_BREAK_NOTIFY_HEADER pLeaseBreakHeader = NULL; // Do not free
    PBYTE pOutBuffer = pSmbResponse->pBuffer;
    ULONG ulBytesAvailable = pSmbResponse->ulBytesAvailable;
    ULONG ulOffset = 0;
    ULONG ulTotalBytesUsed = 0;
    PLWIO_SRV_FILE_2 pFile = NULL;

    // Lock is not held at this point
    ntStatus = SrvAcquireOplockFile_SMB_V2(pOplockState, FALSE, &pFile);
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = SMB2MarshalHeader(
                        pOutBuffer,
                        ulOffset,
                        ulBytesAvailable,
                        COM2_BREAK,
                        pSmbRequest->CreditCharge,
                        pExecContext->usCreditsGranted,
                        0L, /* Process Id  */
                        pSmbRequest->pHeader->ullCommandSequence,
                        0L,  /* Tree Id    */
                        0LL, /* Session Id */
                        0LL, /* Async Id   */
                        STATUS_SUCCESS,
                        TRUE,
                        LwIsSetFlag(
                            pSmbRequest->pHeader->ulFlags,
                            SMB2_FLAGS_RELATED_OPERATION),
                        &pSmbResponse->pHeader,
                        &pSmbResponse->ulHeaderSize);
    BAIL_ON_NT_STATUS(ntStatus);

    pOutBuffer       += pSmbResponse->ulHeaderSize;
    ulOffset         += pSmbResponse->ulHeaderSize;
    ulBytesAvailable -= pSmbResponse->ulHeaderSize;
    ulTotalBytesUsed += pSmbResponse->ulHeaderSize;

    if (ulBytesAvailable < sizeof(SMB2_LEASE_BREAK_NOTIFY_HEADER))
    {
        ntStatus = STATUS_INVALID_NETWORK_RESPONSE;
        BAIL_ON_NT_STATUS(ntStatus);
    }

    pLeaseBreakHeader = (PSMB2_LEASE_BREAK_NOTIFY_HEADER)pOutBuffer;

    pLeaseBreakHeader->Length = COM2_BREAK_LEASE_NOTIFY_STRUCT_SIZE;

    pLeaseBreakHeader->Reserved = 0;

    pLeaseBreakHeader->Flags = 0;

    if (IsSetFlag(
            pOplockState->oplockBuffer_out.LeaseBreakFlags,
            IO_LEASE_FLAG_ACK_REQUIRED))
    {
        pLeaseBreakHeader->Flags = SMB2_LEASE_BREAK_NOTIFY_ACK_REQUIRED;
    }

    LWIO_ASSERT(pOplockState->pLeaseContext != NULL);

    LwRtlCopyMemory(
        &pLeaseBreakHeader->LeaseKey,
        &pOplockState->LeaseContext.Key,
        sizeof(pLeaseBreakHeader->LeaseKey));

    pLeaseBreakHeader->CurrentLeaseState =
        SrvLeaseMapIoToSmb2LeaseState(pOplockState->oplockBuffer_out.CurrentLeaseState);

    pLeaseBreakHeader->NewLeaseState =
        SrvLeaseMapIoToSmb2LeaseState(pOplockState->oplockBuffer_out.NewLeaseState);

    // These three MUST be zero
    pLeaseBreakHeader->BreakReason = 0;
    pLeaseBreakHeader->AccessMaskHint = 0;
    pLeaseBreakHeader->ShareMaskHint = 0;

    // pOutBuffer       += sizeof(SMB2_LEASE_BREAK_NOTIFY_HEADER);
    // ulBytesUsed       = sizeof(SMB2_LEASE_BREAK_NOTIFY_HEADER);
    // ulOffset         += sizeof(SMB2_LEASE_BREAK_NOTIFY_HEADER);
    ulBytesAvailable -= sizeof(SMB2_LEASE_BREAK_NOTIFY_HEADER);
    ulTotalBytesUsed += sizeof(SMB2_LEASE_BREAK_NOTIFY_HEADER);

    pSmbResponse->ulMessageSize = ulTotalBytesUsed;

cleanup:

    if (pFile)
    {
        SrvFile2Release(pFile);
    }

    return ntStatus;

error:

    if (ulTotalBytesUsed)
    {
        pSmbResponse->pHeader      = NULL;
        pSmbResponse->ulHeaderSize = 0;
        memset(pSmbResponse->pBuffer, 0, ulTotalBytesUsed);
    }

    pSmbResponse->ulMessageSize = 0;

    goto cleanup;
}

static
NTSTATUS
SrvBuildOplockBreakResponse_SMB_V2(
    PSRV_EXEC_CONTEXT        pExecContext,
    PSRV_OPLOCK_STATE_SMB_V2 pOplockState,
    UCHAR                    ucOplockLevel
    )
{
    NTSTATUS                   ntStatus      = STATUS_SUCCESS;
    PSRV_EXEC_CONTEXT_SMB_V2   pCtxSmb2      = pExecContext->pProtocolContext;
    ULONG                      iMsg          = pCtxSmb2->iMsg;
    PSRV_MESSAGE_SMB_V2        pSmbRequest   = &pCtxSmb2->pRequests[iMsg];
    PSRV_MESSAGE_SMB_V2        pSmbResponse  = &pCtxSmb2->pResponses[iMsg];
    PSMB2_OPLOCK_BREAK_HEADER  pOplockBreakHeader = NULL; // Do not free
    PBYTE pOutBuffer       = pSmbResponse->pBuffer;
    ULONG ulBytesAvailable = pSmbResponse->ulBytesAvailable;
    ULONG ulOffset         = 0;
    ULONG ulTotalBytesUsed = 0;
    PLWIO_SRV_FILE_2 pFile = NULL;

    // Lock is not held at this point
    ntStatus = SrvAcquireOplockFile_SMB_V2(pOplockState, FALSE, &pFile);
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = SMB2MarshalHeader(
                        pOutBuffer,
                        ulOffset,
                        ulBytesAvailable,
                        COM2_BREAK,
                        pSmbRequest->CreditCharge,
                        pExecContext->usCreditsGranted,
                        pSmbRequest->pHeader->ulPid,
                        pSmbRequest->pHeader->ullCommandSequence,
                        pCtxSmb2->pTree->ulTid,
                        pCtxSmb2->pSession->ullUid,
                        0LL, /* Async Id */
                        STATUS_SUCCESS,
                        TRUE,
                        LwIsSetFlag(
                            pSmbRequest->pHeader->ulFlags,
                            SMB2_FLAGS_RELATED_OPERATION),
                        &pSmbResponse->pHeader,
                        &pSmbResponse->ulHeaderSize);
    BAIL_ON_NT_STATUS(ntStatus);

    pOutBuffer       += pSmbResponse->ulHeaderSize;
    ulOffset         += pSmbResponse->ulHeaderSize;
    ulBytesAvailable -= pSmbResponse->ulHeaderSize;
    ulTotalBytesUsed += pSmbResponse->ulHeaderSize;

    if (ulBytesAvailable < sizeof(SMB2_OPLOCK_BREAK_HEADER))
    {
        ntStatus = STATUS_INVALID_NETWORK_RESPONSE;
        BAIL_ON_NT_STATUS(ntStatus);
    }

    pOplockBreakHeader = (PSMB2_OPLOCK_BREAK_HEADER)pOutBuffer;

    pOplockBreakHeader->usLength = COM2_BREAK_OPLOCK_RESP_STRUCT_SIZE;
    pOplockBreakHeader->fid      = pFile->fid;
    pOplockBreakHeader->ucReserved = 0;
    pOplockBreakHeader->ulReserved = 0;
    pOplockBreakHeader->ucOplockLevel = ucOplockLevel;

    // pOutBuffer       += sizeof(SMB2_OPLOCK_BREAK_HEADER);
    // ulBytesUsed       = sizeof(SMB2_OPLOCK_BREAK_HEADER);
    // ulOffset         += sizeof(SMB2_OPLOCK_BREAK_HEADER);
    // ulBytesAvailable -= sizeof(SMB2_OPLOCK_BREAK_HEADER);
    ulTotalBytesUsed += sizeof(SMB2_OPLOCK_BREAK_HEADER);

    pSmbResponse->ulMessageSize = ulTotalBytesUsed;

cleanup:

    if (pFile)
    {
        SrvFile2Release(pFile);
    }

    return ntStatus;

error:

    if (ulTotalBytesUsed)
    {
        pSmbResponse->pHeader      = NULL;
        pSmbResponse->ulHeaderSize = 0;
        memset(pSmbResponse->pBuffer, 0, ulTotalBytesUsed);
    }

    pSmbResponse->ulMessageSize = 0;

    goto cleanup;
}

static
NTSTATUS
SrvBuildLeaseBreakResponse_SMB_V2(
    IN OUT PSRV_EXEC_CONTEXT        pExecContext,
    IN PSRV_OPLOCK_STATE_SMB_V2 pOplockState,
    IN PSMB2_LEASE_BREAK_ACK_HEADER RequestLeaseBreakHeader
    )
{
    NTSTATUS                   ntStatus      = STATUS_SUCCESS;
    PSRV_EXEC_CONTEXT_SMB_V2   pCtxSmb2      = pExecContext->pProtocolContext;
    ULONG                      iMsg          = pCtxSmb2->iMsg;
    PSRV_MESSAGE_SMB_V2        pSmbRequest   = &pCtxSmb2->pRequests[iMsg];
    PSRV_MESSAGE_SMB_V2        pSmbResponse  = &pCtxSmb2->pResponses[iMsg];
    PSMB2_LEASE_BREAK_ACK_HEADER  pLeaseBreakHeader = NULL; // Do not free
    PBYTE pOutBuffer       = pSmbResponse->pBuffer;
    ULONG ulBytesAvailable = pSmbResponse->ulBytesAvailable;
    ULONG ulOffset         = 0;
    ULONG ulTotalBytesUsed = 0;
    PLWIO_SRV_FILE_2 pFile = NULL;

    // Lock is not held at this point
    ntStatus = SrvAcquireOplockFile_SMB_V2(pOplockState, FALSE, &pFile);
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = SMB2MarshalHeader(
                        pOutBuffer,
                        ulOffset,
                        ulBytesAvailable,
                        COM2_BREAK,
                        pSmbRequest->CreditCharge,
                        pExecContext->usCreditsGranted,
                        pSmbRequest->pHeader->ulPid,
                        pSmbRequest->pHeader->ullCommandSequence,
                        pCtxSmb2->pTree->ulTid,
                        pCtxSmb2->pSession->ullUid,
                        0LL, /* Async Id */
                        STATUS_SUCCESS,
                        TRUE,
                        LwIsSetFlag(
                            pSmbRequest->pHeader->ulFlags,
                            SMB2_FLAGS_RELATED_OPERATION),
                        &pSmbResponse->pHeader,
                        &pSmbResponse->ulHeaderSize);
    BAIL_ON_NT_STATUS(ntStatus);

    pOutBuffer       += pSmbResponse->ulHeaderSize;
    ulOffset         += pSmbResponse->ulHeaderSize;
    ulBytesAvailable -= pSmbResponse->ulHeaderSize;
    ulTotalBytesUsed += pSmbResponse->ulHeaderSize;

    if (ulBytesAvailable < sizeof(SMB2_LEASE_BREAK_ACK_HEADER))
    {
        ntStatus = STATUS_INVALID_NETWORK_RESPONSE;
        BAIL_ON_NT_STATUS(ntStatus);
    }

    pLeaseBreakHeader = (PSMB2_LEASE_BREAK_ACK_HEADER)pOutBuffer;


    pLeaseBreakHeader->Length = COM2_BREAK_LEASE_RESP_STRUCT_SIZE;
    pLeaseBreakHeader->Reserved = 0;
    pLeaseBreakHeader->Flags = 0;

    LwRtlCopyMemory(
        &pLeaseBreakHeader->LeaseKey,
        &RequestLeaseBreakHeader->LeaseKey,
        sizeof(pLeaseBreakHeader->LeaseKey));

    pLeaseBreakHeader->LeaseState = RequestLeaseBreakHeader->LeaseState;
    pLeaseBreakHeader->LeaseDuration = 0;

    // pOutBuffer       += sizeof(SMB2_OPLOCK_BREAK_HEADER);
    // ulBytesUsed       = sizeof(SMB2_OPLOCK_BREAK_HEADER);
    // ulOffset         += sizeof(SMB2_OPLOCK_BREAK_HEADER);
    // ulBytesAvailable -= sizeof(SMB2_OPLOCK_BREAK_HEADER);
    ulTotalBytesUsed += sizeof(SMB2_LEASE_BREAK_ACK_HEADER);

    pSmbResponse->ulMessageSize = ulTotalBytesUsed;

cleanup:

    if (pFile)
    {
        SrvFile2Release(pFile);
    }

    return ntStatus;

error:

    if (ulTotalBytesUsed)
    {
        pSmbResponse->pHeader      = NULL;
        pSmbResponse->ulHeaderSize = 0;
        memset(pSmbResponse->pBuffer, 0, ulTotalBytesUsed);
    }

    pSmbResponse->ulMessageSize = 0;

    goto cleanup;
}

static
VOID
SrvOplockExpiredCB_SMB_V2(
    PSRV_TIMER_REQUEST pTimerRequest,
    PVOID              pUserData
    )
{
    NTSTATUS                 ntStatus     = STATUS_SUCCESS;
    PSRV_OPLOCK_STATE_SMB_V2 pOplockState = pUserData;

    ntStatus = SrvEnqueueOplockAckTask_SMB_V2(pOplockState);
    BAIL_ON_NT_STATUS(ntStatus);

cleanup:

    SrvTimerRelease(&pOplockState->pTimerRequest);
    SrvReleaseOplock(pOplockState);

    return;

error:

    goto cleanup;
}

static
NTSTATUS
SrvEnqueueOplockAckTask_SMB_V2(
    PSRV_OPLOCK_STATE_SMB_V2 pOplockState
    )
{
    NTSTATUS          ntStatus     = STATUS_SUCCESS;
    PSRV_EXEC_CONTEXT pExecContext = NULL;

    ntStatus = SrvBuildOplockExecContext_SMB_V2(
                    pOplockState,
                    LW_SMB2_OPLOCK_ACTION_PROCESS_ACK,
                    FALSE,
                    &pExecContext);
    BAIL_ON_NT_STATUS(ntStatus);

    SrvScheduleExecContext(pExecContext);

error:

    if (pExecContext)
    {
        SrvReleaseExecContext(pExecContext);
    }

    return ntStatus;
}

static
NTSTATUS
SrvSwitchHandleToOplock_SMB_V2(
    PSRV_OPLOCK_STATE_SMB_V2 pOplockState
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    BOOLEAN bInLock = FALSE;

    LWIO_LOCK_MUTEX(bInLock, &pOplockState->mutex);

    if (!pOplockState->pFile)
    {
        status = STATUS_USER_SESSION_DELETED;
        BAIL_ON_NT_STATUS(status);
    }

    if (pOplockState->pAcb && pOplockState->pAcb->AsyncCancelContext)
    {
        // Already the file system registered oplock.
        status = STATUS_SUCCESS;
        goto cleanup;
    }

    LwRtlZeroMemory(
        &pOplockState->oplockBuffer_in,
        sizeof(pOplockState->oplockBuffer_in));

    LwRtlZeroMemory(
        &pOplockState->oplockBuffer_out,
        sizeof(pOplockState->oplockBuffer_out));

    pOplockState->oplockBuffer_in.OplockRequestType =
        IO_OPLOCK_REQUEST_LEASE;
    pOplockState->oplockBuffer_in.RequestLeaseState =
        SrvLeaseMapSmb2ToIoLeaseState(
            pOplockState->Oplock.pLeaseRecord->LeaseState);

    SrvPrepareOplockStateAsync_SMB_V2(pOplockState);

    status = IoFsControlFile(
                    pOplockState->pFile->hFile,
                    pOplockState->pAcb,
                    &pOplockState->ioStatusBlock,
                    IO_FSCTL_OPLOCK_REQUEST,
                    &pOplockState->oplockBuffer_in,
                    sizeof(pOplockState->oplockBuffer_in),
                    &pOplockState->oplockBuffer_out,
                    sizeof(pOplockState->oplockBuffer_out));

    switch (status)
    {
        case STATUS_PENDING:

            status = STATUS_SUCCESS;

            break;

        default:

            SrvReleaseOplockStateAsync_SMB_V2(pOplockState); // completed sync
            BAIL_ON_NT_STATUS(status);

            break;
    }

cleanup:

    LWIO_UNLOCK_MUTEX(bInLock, &pOplockState->mutex);

    return status;

error:

    goto cleanup;
}

static
VOID
SrvOplockSwitchLeaseHandle_SMB_V2(
    IN HANDLE hOplock
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    PSRV_OPLOCK_STATE_SMB_V2 pOplockState = hOplock;
    PSRV_OPLOCK_STATE_SMB_V2 pListOplockState = NULL;
    PSRV_LEASE_RECORD_ITERATOR pIter = NULL;

    status = SrvLeaseRecordIterCreate(
                    pOplockState->Oplock.pLeaseRecord,
                    &pIter);
    BAIL_ON_NT_STATUS(status);

    while ((pListOplockState = SrvLeaseRecordIterNext(pIter)) != NULL)
    {
        if (pListOplockState == pOplockState)
        {
            continue;
        }

        status = SrvSwitchHandleToOplock_SMB_V2(pListOplockState);
        if (status == STATUS_SUCCESS)
        {
            break;
        }
    }

error:

    SrvLeaseRecordIterFree(&pIter);
}

static
VOID
SrvCancelOplock_SMB_V2(
    HANDLE hOplock
    )
{
    PSRV_OPLOCK_STATE_SMB_V2 pOplockState = hOplock;
    BOOLEAN bInLock = FALSE;

    LWIO_LOCK_MUTEX(bInLock, &pOplockState->mutex);

    if (pOplockState->pAcb && pOplockState->pAcb->AsyncCancelContext)
    {

        if (pOplockState->Oplock.pLeaseRecord)
        {
            // We are the file system registered oplock for a lease.
            // If there are other opens for this lease, have one of them
            // assume this role.
            SrvOplockSwitchLeaseHandle_SMB_V2(pOplockState);
        }
            
        IoCancelAsyncCancelContext(pOplockState->pAcb->AsyncCancelContext);
        IoDereferenceAsyncCancelContext(
                &pOplockState->pAcb->AsyncCancelContext);
    }

    if (pOplockState->pTimerRequest)
    {
        NTSTATUS ntStatus = STATUS_SUCCESS;

        ntStatus = SrvTimerCancelRequest(
                        pOplockState->pTimerRequest,
                        NULL);
        if (ntStatus == STATUS_SUCCESS)
        {
            SrvTimerRelease(&pOplockState->pTimerRequest);
            SrvReleaseOplock(pOplockState);
        }
    }

    LWIO_UNLOCK_MUTEX(bInLock, &pOplockState->mutex);
}

static
NTSTATUS
SrvReparentOplock_SMB_V2(
    HANDLE hOplockState,
    PLWIO_SRV_FILE_2 pSrvFile,
    PSRV_DURABLE_ENTRY pDurable
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    PSRV_OPLOCK_STATE_SMB_V2 pOplockState = hOplockState;
    BOOLEAN bInLock = FALSE;
    PLWIO_SRV_FILE_2 pOldFile = NULL;
    LWIO_ASSERT(!LW_IS_BOTH_OR_NEITHER(pSrvFile, pDurable));

    LWIO_LOCK_MUTEX(bInLock, &pOplockState->mutex);

    if (pOplockState->bBreakInProgress || pOplockState->bCancelled)
    {
        status = STATUS_USER_SESSION_DELETED;
        BAIL_ON_NT_STATUS(status);
    }

    pOldFile = pOplockState->pFile;

    if (pSrvFile)
    {
        SrvFile2Acquire(pSrvFile);
    }

    pOplockState->pFile = pSrvFile;
    pOplockState->pDurable = pDurable;

error:

    LWIO_UNLOCK_MUTEX(bInLock, &pOplockState->mutex);

    if (pOldFile)
    {
        SrvFile2Release(pOldFile);
    }

    return status;
}

static
VOID
SrvFreeOplock_SMB_V2(
    HANDLE hOplock
    )
{
    PSRV_OPLOCK_STATE_SMB_V2 pOplockState = hOplock;

    LWIO_ASSERT(!pOplockState->pAcb);
    LWIO_ASSERT(!pOplockState->pTimerRequest);

    SrvLeaseRecordRemoveOplock(pOplockState);

    if (pOplockState->pDurable)
    {
        SrvSmb2DurableClose(
            SrvGetDurableHandleTable(),
            pOplockState->pDurable,
            NULL);
    }

    if (pOplockState->pFile)
    {
        SrvFile2Release(pOplockState->pFile);
    }

    if (pOplockState->pMutex)
    {
        pthread_mutex_destroy(&pOplockState->mutex);
    }

    SrvFreeMemory(pOplockState);
}

static
NTSTATUS
SrvOplockFindOpenFileId(
    IN PSRV_LEASE_RECORD pLeaseRecord,
    OUT PSMB2_FID pFileId
    )
{
    NTSTATUS status = STATUS_NOT_FOUND;
    PSRV_OPLOCK_STATE_SMB_V2 pOplockState = NULL;
    PSRV_LEASE_RECORD_ITERATOR pIter = NULL;
    BOOLEAN bInLock = FALSE;

    status = SrvLeaseRecordIterCreate(pLeaseRecord, &pIter);
    BAIL_ON_NT_STATUS(status);

    while ((pOplockState = SrvLeaseRecordIterNext(pIter)) != NULL)
    {
        LWIO_LOCK_MUTEX(bInLock, &pOplockState->mutex);
        if (pOplockState->bBreakInProgress && pOplockState->pFile)
        {
            *pFileId = pOplockState->pFile->fid;
            status = STATUS_SUCCESS;
        }
        LWIO_UNLOCK_MUTEX(bInLock, &pOplockState->mutex);

        if (status == STATUS_SUCCESS)
        {
            break;
        }
    }

error:

    SrvLeaseRecordIterFree(&pIter);

    return status;
}

static
VOID
SrvOplockCloseLeaseDurableHandles(
    IN PSRV_LEASE_RECORD pLeaseRecord
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    PSRV_OPLOCK_STATE_SMB_V2 pOplockState = NULL;
    PSRV_LEASE_RECORD_ITERATOR pIter = NULL;
    PSRV_DURABLE_ENTRY pDurable = NULL;
    BOOLEAN bInLock = FALSE;

    status = SrvLeaseRecordIterCreate(pLeaseRecord, &pIter);
    BAIL_ON_NT_STATUS(status);

    while ((pOplockState = SrvLeaseRecordIterNext(pIter)) != NULL)
    {
        LWIO_LOCK_MUTEX(bInLock, &pOplockState->mutex);
        pDurable = pOplockState->pDurable;
        pOplockState->pDurable = NULL;
        LWIO_UNLOCK_MUTEX(bInLock, &pOplockState->mutex);

        if (pDurable)
        {
            SrvSmb2DurableClose(SrvGetDurableHandleTable(), pDurable, NULL);
        }
    }

error:

    SrvLeaseRecordIterFree(&pIter);
}

VOID
SrvPrepareOplockStateAsync_SMB_V2(
    PSRV_OPLOCK_STATE_SMB_V2 pOplockState
    )
{
    SrvAcquireOplock(pOplockState);

    pOplockState->acb.Callback           = &SrvOplockAsyncCB_SMB_V2;
    pOplockState->acb.CallbackContext    = pOplockState;
    pOplockState->acb.AsyncCancelContext = NULL;
    pOplockState->pAcb                   = &pOplockState->acb;
}

static
VOID
SrvOplockAsyncCB_SMB_V2(
    PVOID pContext
    )
{
    NTSTATUS                   ntStatus          = STATUS_SUCCESS;
    PSRV_OPLOCK_STATE_SMB_V2   pOplockState      = pContext;
    PSRV_OPLOCK_STATE_SMB_V2   pBreakOplockState = NULL;
    PSRV_EXEC_CONTEXT          pExecContext      = NULL;
    PSRV_DURABLE_ENTRY         pDurable          = NULL;
    PSRV_LEASE_RECORD          pLeaseRecord      = NULL;
    PSRV_LEASE_RECORD_ITERATOR pIter             = NULL;
    BOOLEAN                    bInLock           = FALSE;
    BOOLEAN                    bInBreakLock      = FALSE;

    LWIO_LOCK_MUTEX(bInLock, &pOplockState->mutex);

    if (pOplockState->pAcb && pOplockState->pAcb->AsyncCancelContext)
    {
        IoDereferenceAsyncCancelContext(
            &pOplockState->pAcb->AsyncCancelContext);
    }
    pOplockState->pAcb = NULL;

    switch (pOplockState->ioStatusBlock.Status)
    {
        case STATUS_CANCELLED:
            // File handle has been released and oplock canceled.
            // Full cleanup will occur when the final reference on this
            // oplock is dropped.

            pDurable = pOplockState->pDurable;
            pOplockState->pDurable = NULL;
            pOplockState->bCancelled = TRUE;
            goto cleanup;

        case STATUS_OPLOCK_SWITCHED_TO_NEW_HANDLE:
            // Lease was granted on a new IO_FILE_HANDLE which will now
            // receive any future lease break notifications from the
            // file system.  Verify internal state, but no need to send
            // a break message to the client

            LWIO_ASSERT(pOplockState->pLeaseContext != NULL);
            goto cleanup;

        default:
            ntStatus = pOplockState->ioStatusBlock.Status;
            BAIL_ON_NT_STATUS(ntStatus);
            break;
    }
    
    // Find an active connection for this oplock/lease context.
    // If found, issue break there.  Otherwise, the orphaned durable
    // handle(s) for this oplock/lease context must be closed.
    if (pOplockState->pFile)
    {
        pBreakOplockState = pOplockState;
        bInBreakLock = bInLock;
        bInLock = FALSE;
    }
    else if (pOplockState->Oplock.pLeaseRecord)
    {
        pLeaseRecord = pOplockState->Oplock.pLeaseRecord;

        LWIO_UNLOCK_MUTEX(bInLock, &pOplockState->mutex);

        ntStatus = SrvLeaseRecordIterCreate(pLeaseRecord, &pIter);
        BAIL_ON_NT_STATUS(ntStatus);

        ntStatus = STATUS_NOT_FOUND;
        while ((pBreakOplockState = SrvLeaseRecordIterNext(pIter)) != NULL)
        {
            LWIO_LOCK_MUTEX(bInBreakLock, &pBreakOplockState->mutex);
            if (pBreakOplockState->pFile)
            {
                // Provide IRP results to oplock that will actually
                // perform the work.
                pBreakOplockState->oplockBuffer_out =
                    pOplockState->oplockBuffer_out;
                ntStatus = STATUS_SUCCESS;
                break;
            }
            LWIO_UNLOCK_MUTEX(bInBreakLock, &pBreakOplockState->mutex);
        }
    }
    else
    {
        ntStatus = STATUS_NOT_FOUND;
    }
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = SrvBuildOplockExecContext_SMB_V2(
                    pBreakOplockState,
                    LW_SMB2_OPLOCK_ACTION_SEND_BREAK,
                    bInBreakLock,
                    &pExecContext);
    BAIL_ON_NT_STATUS(ntStatus);

    pBreakOplockState->bBreakInProgress = TRUE;

    LWIO_UNLOCK_MUTEX(bInBreakLock, &pBreakOplockState->mutex);

    SrvScheduleOrExecutePriorityExecContext(pExecContext);

cleanup:

    LWIO_UNLOCK_MUTEX(bInLock, &pOplockState->mutex);
    LWIO_UNLOCK_MUTEX(bInBreakLock, &pBreakOplockState->mutex);

    SrvLeaseRecordIterFree(&pIter);

    if (pExecContext)
    {
        SrvReleaseExecContext(pExecContext);
    }

    if (pOplockState)
    {
        SrvReleaseOplock(pOplockState);
    }

    if (pDurable)
    {
        SrvSmb2DurableClose(SrvGetDurableHandleTable(), pDurable, NULL);
    }

    return;

error:

    // Close any orphaned durable handle outside of this oplock's lock.
    pDurable = pOplockState->pDurable;
    pOplockState->pDurable = NULL;

    // An oplock break on orphaned durable handles is not an error.
    if (ntStatus != STATUS_NOT_FOUND)
    {
        LWIO_LOG_ERROR(
            "Error: Failed processing oplock break (%s)\n",
            LwNtStatusToName(ntStatus));
    }

    LWIO_UNLOCK_MUTEX(bInLock, &pOplockState->mutex);
    LWIO_UNLOCK_MUTEX(bInBreakLock, &pBreakOplockState->mutex);

    if (pLeaseRecord)
    {
        SrvOplockCloseLeaseDurableHandles(pLeaseRecord);
    }

    goto cleanup;
}

static
NTSTATUS
SrvBuildOplockExecContext_SMB_V2(
    PSRV_OPLOCK_STATE_SMB_V2 pOplockState,
    USHORT                   usOplockAction,
    BOOLEAN                  bInLock,
    PSRV_EXEC_CONTEXT*       ppExecContext
    )
{
    NTSTATUS                  ntStatus           = STATUS_SUCCESS;
    PSRV_EXEC_CONTEXT         pExecContext       = NULL;
    PSMB_PACKET               pSmbRequest        = NULL;
    PBYTE                     pBuffer            = NULL;
    ULONG                     ulBytesAvailable   = 0;
    ULONG                     ulOffset           = 0;
    ULONG                     ulBytesUsed        = 0;
    ULONG                     ulTotalBytesUsed   = 0;
    PSMB2_HEADER              pHeader            = NULL; // Do not free
    PSMB2_OPLOCK_BREAK_HEADER pOplockBreakHeader = NULL; // Do not free
    PLWIO_SRV_FILE_2          pFile              = NULL;
    PSRV_CONNECTION           pConnection        = NULL;

    ntStatus = SrvAcquireOplockFile_SMB_V2(pOplockState, bInLock, &pFile);
    BAIL_ON_NT_STATUS(ntStatus);

    pConnection = pFile->pTree->pSession->pConnection;

    ntStatus = SMBPacketAllocate(
                    pConnection->hPacketAllocator,
                    &pSmbRequest);
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = SMBPacketBufferAllocate(
                    pConnection->hPacketAllocator,
                    SMB_PACKET_DEFAULT_SIZE_SMB_V2,
                    &pSmbRequest->pRawBuffer,
                    &pSmbRequest->bufferLen);
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = SMB2InitPacket(pSmbRequest);
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = SrvBuildExecContext(
                    pConnection,
                    pSmbRequest,
                    TRUE,
                    &pExecContext);
    BAIL_ON_NT_STATUS(ntStatus);

    pBuffer          = pSmbRequest->pRawBuffer;
    ulBytesAvailable = pSmbRequest->bufferLen;

    if (ulBytesAvailable < sizeof(NETBIOS_HEADER))
    {
        ntStatus = STATUS_INVALID_NETWORK_RESPONSE;
        BAIL_ON_NT_STATUS(ntStatus);
    }

    pBuffer          += sizeof(NETBIOS_HEADER);
    ulBytesAvailable -= sizeof(NETBIOS_HEADER);

    ntStatus = SMB2MarshalHeader(
                    pBuffer,
                    ulOffset,
                    ulBytesAvailable,
                    COM2_BREAK,
                    0,                    /* credit charge   */
                    pExecContext->usCreditsGranted,
                    0,                    /* pid             */
                    0xFFFFFFFFFFFFFFFFLL, /* mid = -1        */
                    pFile->pTree->ulTid,
                    pFile->pTree->pSession->ullUid,
                    0LL,                  /* Async Id        */
                    STATUS_SUCCESS,
                    FALSE,                /* is response     */
                    FALSE,                /* chained message */
                    &pHeader,
                    &ulBytesUsed);
    BAIL_ON_NT_STATUS(ntStatus);

    pBuffer          += ulBytesUsed;
    ulBytesAvailable -= ulBytesUsed;
    ulTotalBytesUsed += ulBytesUsed;

    if (ulBytesAvailable < sizeof(SMB2_OPLOCK_BREAK_HEADER))
    {
        ntStatus = STATUS_INVALID_NETWORK_RESPONSE;
        BAIL_ON_NT_STATUS(ntStatus);
    }

    pOplockBreakHeader = (PSMB2_OPLOCK_BREAK_HEADER)pBuffer;

    pOplockBreakHeader->fid      = pFile->fid;
    pOplockBreakHeader->usLength = COM2_BREAK_OPLOCK_RESP_STRUCT_SIZE;

    pOplockBreakHeader->ulReserved = usOplockAction;

    // pBuffer          += sizeof(SMB2_OPLOCK_BREAK_HEADER);
    // ulBytesAvailable -= sizeof(SMB2_OPLOCK_BREAK_HEADER);
    ulTotalBytesUsed += sizeof(SMB2_OPLOCK_BREAK_HEADER);

    pSmbRequest->bufferUsed += ulTotalBytesUsed;

    SMBPacketMarshallFooter(pSmbRequest);

    *ppExecContext = pExecContext;

cleanup:

    if (pFile)
    {
        SrvFile2Release(pFile);
    }

    if (pSmbRequest)
    {
        SMBPacketRelease(
                pConnection->hPacketAllocator,
                pSmbRequest);
    }

    return ntStatus;

error:

    *ppExecContext = NULL;

    if (pExecContext)
    {
        SrvReleaseExecContext(pExecContext);
    }

    goto cleanup;
}

VOID
SrvReleaseOplockStateAsync_SMB_V2(
    PSRV_OPLOCK_STATE_SMB_V2 pOplockState
    )
{
    PIO_ASYNC_CONTROL_BLOCK pAcb = pOplockState->pAcb;

    if (pAcb)
    {
        pOplockState->pAcb = NULL;

        pAcb->Callback     = NULL;

        if (pAcb->AsyncCancelContext)
        {
            IoDereferenceAsyncCancelContext(&pAcb->AsyncCancelContext);
        }

        if (pAcb->CallbackContext)
        {
            pAcb->CallbackContext = NULL;
            SrvReleaseOplock(pOplockState);
        }
    }
}

////////////////////////////////////////////////////////////////////////

VOID
SrvOplockSetLevel_SMB_V2(
    IN PLWIO_SRV_FILE_2 File,
    IN UCHAR OplockLevel,
    IN ULONG LeaseState
    )
{
    BOOLEAN fileLocked = FALSE;
    PSRV_OPLOCK_STATE_SMB_V2 oplockState = NULL;

    LWIO_LOCK_RWMUTEX_EXCLUSIVE(fileLocked, &File->mutex);

    oplockState = File->hOplock;
    oplockState->CurrentOplockLevel = OplockLevel;

    if (SMB2_OPLOCK_LEVEL_LEASE == OplockLevel)
    {
        oplockState->LeaseContext.State = LeaseState;
    }

    LWIO_UNLOCK_RWMUTEX(fileLocked, &File->mutex);

    return;
}

////////////////////////////////////////////////////////////////////////

static
VOID
SrvOplockGetLevel_SMB_V2_inlock(
    IN PSRV_OPLOCK_STATE_SMB_V2 pOplockState,
    OUT PUCHAR pOplockLevel,
    OUT OPTIONAL PULONG pLeaseState
    )
{
    *pOplockLevel = pOplockState->CurrentOplockLevel;

    if (pLeaseState &&
        (SMB2_OPLOCK_LEVEL_LEASE == pOplockState->CurrentOplockLevel))
    {
        *pLeaseState = pOplockState->LeaseContext.State;
    }

    return;
}

////////////////////////////////////////////////////////////////////////

VOID
SrvOplockGetLevel_SMB_V2(
    IN PLWIO_SRV_FILE_2 pFile,
    OUT PUCHAR pOplockLevel,
    OUT OPTIONAL PULONG pLeaseState
    )
{
    PSRV_OPLOCK_STATE_SMB_V2 pOplockState = NULL;
    BOOLEAN bFileLocked = FALSE;

    LWIO_LOCK_RWMUTEX_EXCLUSIVE(bFileLocked, &pFile->mutex);

    pOplockState = pFile->hOplock;
    SrvOplockGetLevel_SMB_V2_inlock(pOplockState, pOplockLevel, pLeaseState);

    LWIO_UNLOCK_RWMUTEX(bFileLocked, &pFile->mutex);

    return;
}

////////////////////////////////////////////////////////////////////////

VOID
SrvOplockHandleGetLevel_SMB_V2(
    IN HANDLE OplockStateHandle,
    OUT PUCHAR OplockLevel,
    OUT OPTIONAL PULONG LeaseState
    )
{
    SrvOplockGetLevel_SMB_V2_inlock(
        OplockStateHandle,
        OplockLevel,
        LeaseState);

    return;
}
