/* -*- 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 - SMBV1
 *
 *        Opportunistic locks
 *
 * Authors: Sriram Nambakam (snambakam@likewise.com)
 *
 */

#include "includes.h"

static
NTSTATUS
SrvBuildOplockBreakResponse(
    PSRV_EXEC_CONTEXT        pExecContext,
    PSRV_OPLOCK_STATE_SMB_V1 pOplockState,
    UCHAR                    ucOplockLevel
    );

static
VOID
SrvOplockExpiredCB(
    PSRV_TIMER_REQUEST pTimerRequest,
    PVOID              pUserData
    );

static
NTSTATUS
SrvEnqueueOplockAckTask(
    PSRV_OPLOCK_STATE_SMB_V1 pOplockState
    );

static
VOID
SrvCancelOplock_SMB_V1(
    HANDLE hOplock
    );

static
VOID
SrvFreeOplock_SMB_V1(
    HANDLE hOplock
    );

static
VOID
SrvOplockAsyncCB(
    PVOID pContext
    );

static
NTSTATUS
SrvBuildOplockExecContext(
    PSRV_OPLOCK_STATE_SMB_V1 pOplockState,
    USHORT                   usOplockAction,
    PSRV_EXEC_CONTEXT*       ppExecContext
    );

NTSTATUS
SrvBuildOplockState(
    PLWIO_SRV_CONNECTION      pConnection,
    PLWIO_SRV_SESSION         pSession,
    PLWIO_SRV_TREE            pTree,
    PLWIO_SRV_FILE            pFile,
    USHORT                    usFlags2,
    PSRV_OPLOCK_STATE_SMB_V1* ppOplockState
    )
{
    NTSTATUS                 ntStatus     = STATUS_SUCCESS;
    PSRV_OPLOCK_STATE_SMB_V1 pOplockState = NULL;

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

    pOplockState->Oplock.refCount = 1;
    pOplockState->Oplock.pfnCancelOplock = SrvCancelOplock_SMB_V1;
    pOplockState->Oplock.pfnFreeOplock = SrvFreeOplock_SMB_V1;

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

    pOplockState->pConnection = SrvConnectionAcquire(pConnection);

    pOplockState->usFlags2 = usFlags2;
    pOplockState->usUid    = pSession->uid;
    pOplockState->usTid    = pTree->tid;
    pOplockState->usFid    = pFile->fid;

    *ppOplockState = pOplockState;

cleanup:

    return ntStatus;

error:

    *ppOplockState = NULL;

    if (pOplockState)
    {
        SrvReleaseOplock(pOplockState);
    }

    goto cleanup;
}

NTSTATUS
SrvProcessOplock(
    PSRV_EXEC_CONTEXT pExecContext
    )
{
    NTSTATUS                   ntStatus     = 0;
    PLWIO_SRV_CONNECTION       pConnection  = pExecContext->pConnection;
    PSRV_EXEC_CONTEXT_SMB_V1   pCtxSmb1     = pExecContext->pProtocolContext;
    ULONG               iMsg           = pCtxSmb1->iMsg;
    PSRV_MESSAGE_SMB_V1 pSmbRequest    = &pCtxSmb1->pRequests[iMsg];
    PLW_OPLOCK_HEADER   pRequestHeader = NULL; // Do not free
    PBYTE pBuffer          = pSmbRequest->pBuffer + pSmbRequest->usHeaderSize;
    ULONG ulOffset         = pSmbRequest->usHeaderSize;
    ULONG ulBytesAvailable = pSmbRequest->ulMessageSize - pSmbRequest->usHeaderSize;
    PLWIO_SRV_SESSION        pSession      = NULL;
    PLWIO_SRV_TREE           pTree         = NULL;
    PLWIO_SRV_FILE           pFile         = NULL;
    PSRV_OPLOCK_STATE_SMB_V1 pOplockState  = NULL;
    UCHAR                    ucOplockLevel = SMB_OPLOCK_LEVEL_NONE;
    BOOLEAN                  bFileLocked   = FALSE;

    ntStatus = SrvConnectionFindSession_SMB_V1(
                            pCtxSmb1,
                            pConnection,
                            pSmbRequest->pHeader->uid,
                            &pSession);
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = SrvSessionFindTree_SMB_V1(
                    pCtxSmb1,
                    pSession,
                    pSmbRequest->pHeader->tid,
                    &pTree);
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = WireUnmarshallOplockRequest(
                    pBuffer,
                    ulBytesAvailable,
                    ulOffset,
                    &pRequestHeader);
    BAIL_ON_NT_STATUS(ntStatus);

    /*
       Failure to find the file could be normal if the file was
       closed between the break and the processing here.  The
       SMBclose processing handles the rundown any of outstanding
       oplock break processing. In the future, we will squash
       STATUS_NOT_FOUND on failure to prevent unnecessary noise
       in the logs.
    */

    ntStatus = SrvTreeFindFile_SMB_V1(
                    pCtxSmb1,
                    pTree,
                    pRequestHeader->usFid,
                    &pFile);
    BAIL_ON_NT_STATUS(ntStatus);

    switch (pRequestHeader->usAction)
    {
        case LW_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 = SMB_OPLOCK_LEVEL_BROKEN_TO_NONE;

                    break;

                case IO_OPLOCK_BROKEN_TO_LEVEL_2:

                    ucOplockLevel = SMB_OPLOCK_LEVEL_BROKEN_TO_LEVEL_II;

                    break;

                default:

                    ntStatus = STATUS_INTERNAL_ERROR;
                    BAIL_ON_NT_STATUS(ntStatus);

                    break;
            }

            ntStatus = SrvBuildOplockBreakResponse(
                            pExecContext,
                            pOplockState,
                            ucOplockLevel);
            BAIL_ON_NT_STATUS(ntStatus);

            switch (SrvFileGetOplockLevel(pFile)) // current op-lock level
            {
                case SMB_OPLOCK_LEVEL_I:
                case SMB_OPLOCK_LEVEL_BATCH:
                    {
                        LONG64 llExpiry = 0LL;

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

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

                        SrvAcquireOplock(pOplockState);

                        ntStatus = SrvTimerPostRequest(
                                        llExpiry,
                                        pOplockState,
                                        &SrvOplockExpiredCB,
                                        &pOplockState->pTimerRequest);
                        if (ntStatus != STATUS_SUCCESS)
                        {
                            SrvReleaseOplock(pOplockState);
                        }
                        BAIL_ON_NT_STATUS(ntStatus);
                    }

                    break;

                case SMB_OPLOCK_LEVEL_II:
                {
                    PSRV_OPLOCK_STATE_SMB_V1 pOplockState2 = NULL;

                    /* Level2 can only break to none. No Ack needed.
                       Remove any remaining oplock state */

                    pOplockState2 = SrvFileRemoveOplock(pFile);
                    if (pOplockState2)
                    {
                        SrvReleaseOplock(pOplockState2);
                        pOplockState2 = NULL;
                    }

                    ntStatus = STATUS_SUCCESS;

                    break;
                }

                case SMB_OPLOCK_LEVEL_NONE:
                default:

                    ntStatus = STATUS_INTERNAL_ERROR;
                    BAIL_ON_NT_STATUS(ntStatus);

                    break;
            }

            break;

        case LW_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)
            {
                ntStatus = SrvAcknowledgeOplockBreak(
                               pCtxSmb1,
                               pOplockState,
                               NULL,
                               FALSE);
                BAIL_ON_NT_STATUS(ntStatus);
            }

            break;

        default:

            ntStatus = STATUS_INTERNAL_ERROR;
            BAIL_ON_NT_STATUS(ntStatus);
    }

cleanup:

    if (pOplockState)
    {
        SrvReleaseOplock(pOplockState);
    }


    if (pFile)
    {
        SrvFileRelease(pFile);
    }

    if (pTree)
    {
        SrvTreeRelease(pTree);
    }

    if (pSession)
    {
        SrvSessionRelease(pSession);
    }

    return ntStatus;

error:

    goto cleanup;
}

NTSTATUS
SrvAcknowledgeOplockBreak(
    PSRV_EXEC_CONTEXT_SMB_V1 pCtxSmb1,
    PSRV_OPLOCK_STATE_SMB_V1 pOplockState,
    PUCHAR                   pucNewOplockLevel,
    BOOLEAN                  bFileIsClosed
    )
{
    NTSTATUS ntStatus      = STATUS_SUCCESS;
    UCHAR    ucOplockLevel = SMB_OPLOCK_LEVEL_NONE;
    PLWIO_SRV_SESSION pSession = NULL;
    PLWIO_SRV_TREE pTree   = NULL;
    PLWIO_SRV_FILE pFile   = NULL;

    ntStatus = SrvConnectionFindSession_SMB_V1(
                   pCtxSmb1,
                   pOplockState->pConnection,
                   pOplockState->usUid,
                   &pSession);
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = SrvSessionFindTree_SMB_V1(
                   pCtxSmb1,
                   pSession,
                   pOplockState->usTid,
                   &pTree);
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = SrvTreeFindFile(
                    pTree,
                    pOplockState->usFid,
                    &pFile);
    BAIL_ON_NT_STATUS(ntStatus);

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

            ucOplockLevel = SMB_OPLOCK_LEVEL_NONE;

            break;

        case IO_OPLOCK_BROKEN_TO_LEVEL_2:

            ucOplockLevel = SMB_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 == SMB_OPLOCK_LEVEL_NONE) &&
            (ucOplockLevel == SMB_OPLOCK_LEVEL_II))
        {
            pOplockState->oplockBuffer_ack.Response =
                                                IO_OPLOCK_BREAK_ACK_NO_LEVEL_2;
        }
        else
        {
            pOplockState->oplockBuffer_ack.Response =
                                                IO_OPLOCK_BREAK_ACKNOWLEDGE;
        }
    }

    SrvPrepareOplockStateAsync(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:

            SrvAcquireOplock(pOplockState);

            ntStatus = SrvFileSetOplock(pFile, &pOplockState->Oplock);
            if (ntStatus != STATUS_SUCCESS)
            {
                SrvReleaseOplock(pOplockState);
            }
            BAIL_ON_NT_STATUS(ntStatus);


            SrvFileSetOplockLevel(pFile, ucOplockLevel);

            ntStatus = STATUS_SUCCESS;

            break;

        default:

            SrvReleaseOplockStateAsync(pOplockState); // completed sync

            BAIL_ON_NT_STATUS(ntStatus);

            break;
    }

cleanup:

    if (pSession)
    {
        SrvSessionRelease(pSession);
    }

    if (pTree)
    {
        SrvTreeRelease(pTree);
    }

    if (pFile)
    {
        SrvFileRelease(pFile);
    }

    return ntStatus;

error:

    goto cleanup;
}

static
NTSTATUS
SrvBuildOplockBreakResponse(
    PSRV_EXEC_CONTEXT        pExecContext,
    PSRV_OPLOCK_STATE_SMB_V1 pOplockState,
    UCHAR                    ucOplockLevel
    )
{
    NTSTATUS                   ntStatus     = STATUS_SUCCESS;
    PSRV_EXEC_CONTEXT_SMB_V1   pCtxSmb1     = pExecContext->pProtocolContext;
    ULONG                      iMsg         = pCtxSmb1->iMsg;
    PSRV_MESSAGE_SMB_V1        pSmbRequest  = &pCtxSmb1->pRequests[iMsg];
    PSRV_MESSAGE_SMB_V1        pSmbResponse = &pCtxSmb1->pResponses[iMsg];
    PSMB_LOCKING_ANDX_REQUEST_HEADER pRequestHeader = NULL; // Do not free
    PBYTE pOutBuffer           = pSmbResponse->pBuffer;
    ULONG ulBytesAvailable     = pSmbResponse->ulBytesAvailable;
    ULONG ulOffset             = 0;
    ULONG ulTotalBytesUsed     = 0;

    if (!pSmbResponse->ulSerialNum)
    {
        ntStatus = SrvMarshalHeader_SMB_V1(
                        pOutBuffer,
                        ulOffset,
                        ulBytesAvailable,
                        COM_LOCKING_ANDX,
                        STATUS_SUCCESS,
                        FALSE,
                        (pSmbRequest->pHeader->flags2 & FLAG2_ERR_STATUS) != 0,
                        pExecContext->pConnection->serverProperties.ulCapabilities,
                        pCtxSmb1->pTree->tid,
                        SMB_V1_GET_PROCESS_ID(pSmbRequest->pHeader),
                        pCtxSmb1->pSession->uid,
                        pSmbRequest->pHeader->mid,
                        &pSmbResponse->pHeader,
                        &pSmbResponse->pWordCount,
                        &pSmbResponse->pAndXHeader,
                        &pSmbResponse->usHeaderSize);
    }
    else
    {
        ntStatus = SrvMarshalHeaderAndX_SMB_V1(
                        pOutBuffer,
                        ulOffset,
                        ulBytesAvailable,
                        COM_LOCKING_ANDX,
                        &pSmbResponse->pWordCount,
                        &pSmbResponse->pAndXHeader,
                        &pSmbResponse->usHeaderSize);
    }
    BAIL_ON_NT_STATUS(ntStatus);

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

    *pSmbResponse->pWordCount = 8;

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

    pRequestHeader = (PSMB_LOCKING_ANDX_REQUEST_HEADER)pOutBuffer;

    pRequestHeader->ucLockType    = LWIO_LOCK_TYPE_OPLOCK_RELEASE;
    pRequestHeader->ucOplockLevel = ucOplockLevel;
    pRequestHeader->usFid         = pOplockState->usFid;
    pRequestHeader->usNumLocks    = 0;
    pRequestHeader->usNumUnlocks  = 0;
    pRequestHeader->ulTimeout     = 0;
    pRequestHeader->usByteCount   = 0;

    // pOutBuffer       += usBytesUsed;
    // ulOffset         += usBytesUsed;
    // ulBytesAvailable -= usBytesUsed;
    ulTotalBytesUsed += sizeof(SMB_LOCKING_ANDX_REQUEST_HEADER);

    pSmbResponse->ulMessageSize = ulTotalBytesUsed;

cleanup:

    return ntStatus;

error:

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

    pSmbResponse->ulMessageSize = 0;

    goto cleanup;
}

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

    if (pOplockState->pTimerRequest == pTimerRequest)
    {
        SrvTimerRelease(&pOplockState->pTimerRequest);

        ntStatus = SrvEnqueueOplockAckTask(pOplockState);
        BAIL_ON_NT_STATUS(ntStatus);
    }

cleanup:

    SrvReleaseOplock(pOplockState);

    return;

error:

    goto cleanup;
}

static
NTSTATUS
SrvEnqueueOplockAckTask(
    PSRV_OPLOCK_STATE_SMB_V1 pOplockState
    )
{
    NTSTATUS          ntStatus     = STATUS_SUCCESS;
    PSRV_EXEC_CONTEXT pExecContext = NULL;

    ntStatus = SrvBuildOplockExecContext(
                    pOplockState,
                    LW_OPLOCK_ACTION_PROCESS_ACK,
                    &pExecContext);
    BAIL_ON_NT_STATUS(ntStatus);

    SrvScheduleExecContext(pExecContext);

error:

    if (pExecContext)
    {
        SrvReleaseExecContext(pExecContext);
    }

    return ntStatus;
}

static
VOID
SrvCancelOplockState(
    PSRV_OPLOCK_STATE_SMB_V1 pOplockState
    )
{
    BOOLEAN bInLock = FALSE;

    LWIO_LOCK_MUTEX(bInLock, &pOplockState->mutex);

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

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

        ntStatus = SrvTimerCancelRequest(
                        pOplockState->pTimerRequest,
                        NULL);

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

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

VOID
SrvCancelOplock_SMB_V1(
    HANDLE hOplock
    )
{
    SrvCancelOplockState(hOplock);
}

static
VOID
SrvFreeOplock_SMB_V1(
    HANDLE hOplock
    )
{
    PSRV_OPLOCK_STATE_SMB_V1 pOplockState = hOplock;

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

    if (pOplockState->pConnection)
    {
        SrvConnectionRelease(pOplockState->pConnection);
    }

    if (pOplockState->pTimerRequest)
    {
        SrvTimerRelease(&pOplockState->pTimerRequest);
    }

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

    SrvFreeMemory(pOplockState);
}

VOID
SrvPrepareOplockStateAsync(
    PSRV_OPLOCK_STATE_SMB_V1 pOplockState
    )
{
    pOplockState->acb.Callback        = &SrvOplockAsyncCB;

    pOplockState->acb.CallbackContext = pOplockState;
    SrvAcquireOplock(pOplockState);

    pOplockState->acb.AsyncCancelContext = NULL;

    pOplockState->pAcb = &pOplockState->acb;
}

static
VOID
SrvOplockAsyncCB(
    PVOID pContext
    )
{
    NTSTATUS                 ntStatus     = STATUS_SUCCESS;
    PSRV_OPLOCK_STATE_SMB_V1 pOplockState = (PSRV_OPLOCK_STATE_SMB_V1)pContext;
    PSRV_EXEC_CONTEXT        pExecContext = NULL;
    BOOLEAN                  bInLock      = FALSE;

    // TODO: make this function truly no-fail.

    LWIO_LOCK_MUTEX(bInLock, &pOplockState->mutex);

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

    pOplockState->pAcb = NULL;

    /* Nothing to do if this was cancelled */

    if (pOplockState->ioStatusBlock.Status == STATUS_CANCELLED)
    {
        ntStatus = STATUS_SUCCESS;
        goto cleanup;
    }

    ntStatus = pOplockState->ioStatusBlock.Status;
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = SrvBuildOplockExecContext(
                    pOplockState,
                    LW_OPLOCK_ACTION_SEND_BREAK,
                    &pExecContext);
    BAIL_ON_NT_STATUS(ntStatus);

    LWIO_UNLOCK_MUTEX(bInLock, &pOplockState->mutex);
    SrvScheduleOrExecutePriorityExecContext(pExecContext);

cleanup:

    LWIO_UNLOCK_MUTEX(bInLock, &pOplockState->mutex);

    if (pExecContext)
    {
        SrvReleaseExecContext(pExecContext);
    }

    if (pOplockState)
    {
        SrvReleaseOplock(pOplockState);
    }

    return;

error:

    LWIO_LOG_ERROR(
        "Error: Failed processing oplock break [status:0x%x]",
        LwNtStatusToName(ntStatus));

    // TODO: indicate error on file handle somehow

    goto cleanup;
}

static
NTSTATUS
SrvBuildOplockExecContext(
    PSRV_OPLOCK_STATE_SMB_V1 pOplockState,
    USHORT                   usOplockAction,
    PSRV_EXEC_CONTEXT*       ppExecContext
    )
{
    NTSTATUS                 ntStatus         = STATUS_SUCCESS;
    PLWIO_SRV_CONNECTION     pConnection      = pOplockState->pConnection;
    PSRV_EXEC_CONTEXT        pExecContext     = NULL;
    PSMB_PACKET              pSmbRequest      = NULL;
    PBYTE                    pBuffer          = NULL;
    ULONG                    ulBytesAvailable = 0;
    ULONG                    ulOffset         = 0;
    USHORT                   usBytesUsed      = 0;
    USHORT                   usTotalBytesUsed = 0;
    PSMB_HEADER              pHeader          = NULL; // Do not free
    PBYTE                    pWordCount       = NULL; // Do not free
    PANDX_HEADER             pAndXHeader      = NULL; // Do not free
    PLW_OPLOCK_HEADER        pLwOplockHeader  = NULL; // Do not free

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

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

    ntStatus = SrvInitPacket_SMB_V1(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 = SrvMarshalHeader_SMB_V1(
                    pBuffer,
                    ulOffset,
                    ulBytesAvailable,
                    COM_LW_OPLOCK,
                    STATUS_SUCCESS,
                    FALSE,  /* not a response */
                    (pOplockState->usFlags2 & FLAG2_ERR_STATUS) != 0,
                    pConnection->serverProperties.ulCapabilities,
                    pOplockState->usTid,
                    0,      /* pid */
                    pOplockState->usUid,
                    0xFFFF, /* mid = -1 */
                    &pHeader,
                    &pWordCount,
                    &pAndXHeader,
                    &usBytesUsed);
    BAIL_ON_NT_STATUS(ntStatus);

    pBuffer          += usBytesUsed;
    ulBytesAvailable -= usBytesUsed;
    usTotalBytesUsed += usBytesUsed;

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

    pLwOplockHeader = (PLW_OPLOCK_HEADER)pBuffer;

    pLwOplockHeader->usFid    = pOplockState->usFid;
    pLwOplockHeader->usAction = usOplockAction;

    *pWordCount = 1;

    // pBuffer          += sizeof(LW_OPLOCK_HEADER);
    // ulBytesAvailable -= sizeof(LW_OPLOCK_HEADER);
    usTotalBytesUsed += sizeof(LW_OPLOCK_HEADER);

    pSmbRequest->bufferUsed += usTotalBytesUsed;

    SMBPacketMarshallFooter(pSmbRequest);

    if (pConnection->pSMB1SigningKey && SrvConnectionShouldSign(pConnection))
    {
        SMBPacketSign(
            pSmbRequest,
            pSmbRequest->sequence,
            pConnection->pSMB1SigningKey,
            pConnection->ulSMB1SigningKeyLength);
    }
    
    *ppExecContext = pExecContext;

cleanup:

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

    return ntStatus;

error:

    *ppExecContext = NULL;

    if (pExecContext)
    {
        SrvReleaseExecContext(pExecContext);
    }

    goto cleanup;
}

VOID
SrvReleaseOplockStateAsync(
    PSRV_OPLOCK_STATE_SMB_V1 pOplockState
    )
{
    if (pOplockState->pAcb)
    {
        pOplockState->acb.Callback        = NULL;

        if (pOplockState->pAcb->CallbackContext)
        {
            SrvReleaseOplock(pOplockState);

            pOplockState->pAcb->CallbackContext = NULL;
        }

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

        pOplockState->pAcb = NULL;
    }
}

