/* -*- 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
 */

#include "includes.h"

// Case 1: Fail immediately
//
//         All lock requests are posted synchronously
//
// Case 2: Wait indefinitely
//
//         All lock requests are posted asynchronously
//         When all the callbacks are received, a response is sent to the client
//
// Case 3: Wait on a timeout
//
//         All lock requests are posted asynchronously.
//
//         If the timeout happens first, the lock request is set to be expired.
//         And a failure message is sent immediately. An attempt is made to
//         cancel any asynchronous requests.
//
//         If all requests are fulfilled before the timer expires, a response
//         is sent to the client immediately. An attempt is made to cancel the
//         timer.

typedef BOOLEAN (LOCK_MATCH_FUNC)(
    IN HANDLE                 hMatchArg,
    IN PSRV_LOCK_STATE_SMB_V1 pLockState,
    IN PUSHORT                pusLockIndex
    );
typedef LOCK_MATCH_FUNC *PLOCK_MATCH_FUNC;

static LOCK_MATCH_FUNC SrvMatchesPendingLargeUnlockState;
static LOCK_MATCH_FUNC SrvMatchesPendingLargeLockState;
static LOCK_MATCH_FUNC SrvMatchesPendingUnlockState;
static LOCK_MATCH_FUNC SrvMatchesPendingLockState;

static
VOID
SrvFreePendingLockStateList(
    PSRV_BYTE_RANGE_LOCK_STATE_LIST pBRLStateList
    );

static
NTSTATUS
SrvBuildLockState(
    PSMB_LOCKING_ANDX_REQUEST_HEADER pRequestHeader,
    PLOCKING_ANDX_RANGE_LARGE_FILE   pUnlockRangeLarge,
    PLOCKING_ANDX_RANGE_LARGE_FILE   pLockRangeLarge,
    PLOCKING_ANDX_RANGE              pUnlockRange,
    PLOCKING_ANDX_RANGE              pLockRange,
    PLWIO_SRV_FILE                   pFile,
    ULONG                            ulPid,
    USHORT                           usMid,
    PSRV_LOCK_STATE_SMB_V1*          ppLockState
    );

static
NTSTATUS
SrvRegisterPendingLockState(
    PSRV_EXEC_CONTEXT               pExecContext,
    PLWIO_SRV_TREE                  pTree,
    PSRV_BYTE_RANGE_LOCK_STATE_LIST pBRLStateList,
    PSRV_LOCK_STATE_SMB_V1          pLockState
    );

static
NTSTATUS
SrvFindLockState(
    PLOCK_MATCH_FUNC                pMatchFunc,
    HANDLE                          hMatchArg,
    PLWIO_SRV_TREE                  pTree,
    PSRV_BYTE_RANGE_LOCK_STATE_LIST pBRLStateList,
    PSRV_EXEC_CONTEXT*              ppExecContext,
    PSRV_LOCK_STATE_SMB_V1*         ppLockState,
    PUSHORT                         pusLockIndex
    );

static
NTSTATUS
SrvUnregisterBRLState(
    PSRV_BYTE_RANGE_LOCK_STATE_LIST pBRLStateList,
    PSRV_LOCK_STATE_SMB_V1          pLockState
    );

static
NTSTATUS
SrvExecuteLockCancellation(
    PSRV_EXEC_CONTEXT pExecContext
    );

static
VOID
SrvCancelLockState_inlock(
    PSRV_EXEC_CONTEXT pExecContext,
    PSRV_LOCK_STATE_SMB_V1 pLockState
    );

static
VOID
SrvFreeLockTimerRequest_inlock(
    PSRV_LOCK_STATE_SMB_V1 pLockState
    );

static
VOID
SrvClearLockAsyncState_inlock(
    PLWIO_SRV_TREE         pTree,
    PSRV_LOCK_STATE_SMB_V1 pLockState
    );

static
NTSTATUS
SrvExecuteLockRequest(
    PSRV_EXEC_CONTEXT pExecContext
    );

static
VOID
SrvLockExpiredCB(
    PSRV_TIMER_REQUEST pTimerRequest,
    PVOID              pUserData
    );

static
VOID
SrvClearLocks(
    PSRV_LOCK_STATE_SMB_V1 pLockState
    );

static
NTSTATUS
SrvBuildLockingAndXResponse(
    PSRV_EXEC_CONTEXT pExecContext
    );

static
VOID
SrvFreeLockStateHandle(
    HANDLE hState
    );

static
VOID
SrvFreeLockState(
    PSRV_LOCK_STATE_SMB_V1 pLockState
    );

static
VOID
SrvConvertLockTimeout(
    PSRV_LOCK_STATE_SMB_V1 pLockState
    );

NTSTATUS
SrvCreatePendingLockStateList(
    PSRV_BYTE_RANGE_LOCK_STATE_LIST* ppBRLStateList
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PSRV_BYTE_RANGE_LOCK_STATE_LIST pBRLStateList = NULL;

    ntStatus = SrvAllocateMemory(
                    sizeof(SRV_BYTE_RANGE_LOCK_STATE_LIST),
                    OUT_PPVOID(&pBRLStateList));
    BAIL_ON_NT_STATUS(ntStatus);

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

    *ppBRLStateList = pBRLStateList;

cleanup:

    return ntStatus;

error:

    *ppBRLStateList = NULL;

    if (pBRLStateList)
    {
        SrvFreePendingLockStateList(pBRLStateList);
    }

    goto cleanup;
}

VOID
SrvFreePendingLockStateListHandle(
    HANDLE hBRLStateList
    )
{
    SrvFreePendingLockStateList(hBRLStateList);
}

static
VOID
SrvFreePendingLockStateList(
    PSRV_BYTE_RANGE_LOCK_STATE_LIST pBRLStateList
    )
{
    while (pBRLStateList->pHead)
    {
        PSRV_BYTE_RANGE_LOCK_STATE pBRLState = pBRLStateList->pHead;

        pBRLStateList->pHead = pBRLStateList->pHead->pNext;

        // TODO: Cancel the asynchronous state?

        SrvFreeMemory(pBRLState);
    }

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

    SrvFreeMemory(pBRLStateList);
}

NTSTATUS
SrvProcessLockAndX(
    PSRV_EXEC_CONTEXT pExecContext
    )
{
    NTSTATUS                   ntStatus     = 0;
    PLWIO_SRV_CONNECTION       pConnection  = pExecContext->pConnection;
    PSRV_EXEC_CONTEXT_SMB_V1   pCtxSmb1     = pExecContext->pProtocolContext;
    PSRV_LOCK_STATE_SMB_V1     pLockState   = pCtxSmb1->hState;
    PLWIO_SRV_SESSION          pSession     = NULL;
    PLWIO_SRV_TREE             pTree        = NULL;
    PLWIO_SRV_FILE             pFile        = NULL;
    PSRV_OPLOCK_STATE_SMB_V1   pOplockState = NULL;
    PSMB_LOCKING_ANDX_REQUEST_HEADER pRequestHeader = NULL;

    if (pLockState)
    {
        pRequestHeader = pLockState->pRequestHeader;
    }
    else
    {
        ULONG               iMsg         = pCtxSmb1->iMsg;
        PSRV_MESSAGE_SMB_V1 pSmbRequest  = &pCtxSmb1->pRequests[iMsg];
        PBYTE pBuffer          = pSmbRequest->pBuffer +
                                 pSmbRequest->usHeaderSize;
        ULONG ulOffset         = pSmbRequest->usHeaderSize;
        ULONG ulBytesAvailable = pSmbRequest->ulMessageSize -
                                 pSmbRequest->usHeaderSize;
        PLOCKING_ANDX_RANGE              pUnlockRange = NULL;    // Do not free
        PLOCKING_ANDX_RANGE_LARGE_FILE   pUnlockRangeLarge = NULL;// Do not free
        PLOCKING_ANDX_RANGE              pLockRange = NULL;      // Do not free
        PLOCKING_ANDX_RANGE_LARGE_FILE   pLockRangeLarge = NULL; // Do not free

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

        ntStatus = SrvSetStatSessionInfo(pExecContext, pSession);
        BAIL_ON_NT_STATUS(ntStatus);

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

        ntStatus = WireUnmarshallLockingAndXRequest(
                        pBuffer,
                        ulBytesAvailable,
                        ulOffset,
                        &pRequestHeader,
                        &pUnlockRange,
                        &pUnlockRangeLarge,
                        &pLockRange,
                        &pLockRangeLarge);
        BAIL_ON_NT_STATUS(ntStatus);

        SRV_LOG_DEBUG(
                pExecContext->pLogContext,
                SMB_PROTOCOL_VERSION_1,
                pSmbRequest->pHeader->command,
                "Lock request: command(%u),uid(%u),mid(%u),pid(%u),tid(%u),"
                "file-id(%u),locks(%u),unlocks(%u),lockType(0x%x),"
                "oplock-level(0x%x),timeout(%u)",
                pSmbRequest->pHeader->command,
                pSmbRequest->pHeader->uid,
                pSmbRequest->pHeader->mid,
                SMB_V1_GET_PROCESS_ID(pSmbRequest->pHeader),
                pSmbRequest->pHeader->tid,
                pRequestHeader->usFid,
                pRequestHeader->usNumLocks,
                pRequestHeader->usNumUnlocks,
                pRequestHeader->ucLockType,
                pRequestHeader->ucOplockLevel,
                pRequestHeader->ulTimeout);

        //
        // Check for cancel after logging but before any work to avoid
        // doing extra work in case the request was cancelled before
        // starting processing.
        //
        if (SrvMpxTrackerIsCancelledExecContext(pExecContext))
        {
            ntStatus = STATUS_CANCELLED;
            BAIL_ON_NT_STATUS(ntStatus);
        }

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

        ntStatus = SrvBuildLockState(
                        pRequestHeader,
                        pUnlockRangeLarge,
                        pLockRangeLarge,
                        pUnlockRange,
                        pLockRange,
                        pFile,
                        SMB_V1_GET_PROCESS_ID(pSmbRequest->pHeader),
                        pSmbRequest->pHeader->mid,
                        &pLockState);
        BAIL_ON_NT_STATUS(ntStatus);

        pCtxSmb1->hState = pLockState;
        pCtxSmb1->pfnStateRelease = &SrvFreeLockStateHandle;
    }

    switch (pLockState->stage)
    {
        case SRV_LOCK_STAGE_SMB_V1_INITIAL:

            if (pRequestHeader->ucLockType & LWIO_LOCK_TYPE_CHANGE_LOCK_TYPE)
            {
                ntStatus = ERROR_ATOMIC_LOCKS_NOT_SUPPORTED;
                BAIL_ON_NT_STATUS(ntStatus);
            }
            else if (pRequestHeader->ucLockType & LWIO_LOCK_TYPE_CANCEL_LOCK)
            {
                if (!pRequestHeader->usNumLocks &&
                    !pRequestHeader->usNumUnlocks)
                {
                    ntStatus = STATUS_INVALID_PARAMETER;
                    BAIL_ON_NT_STATUS(ntStatus);
                }
            }
            else if (pRequestHeader->ucLockType & LWIO_LOCK_TYPE_OPLOCK_RELEASE)
            {
                pOplockState = SrvFileRemoveOplock(pLockState->pFile);

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

                    ntStatus = SrvAcknowledgeOplockBreak(
                                   pCtxSmb1,
                                   pOplockState,
                                   &pRequestHeader->ucOplockLevel,
                                   FALSE);
                    BAIL_ON_NT_STATUS(ntStatus);
                }
                else
                {
                    // 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);
                }

                goto cleanup;
            }
            else
            {
                PSRV_BYTE_RANGE_LOCK_STATE_LIST pBRLStateList =
                    (PSRV_BYTE_RANGE_LOCK_STATE_LIST)
                    pCtxSmb1->pFile->hCancellableBRLStateList;

                SrvConvertLockTimeout(pLockState);

                switch (pRequestHeader->ulTimeout)
                {
                    case 0:           /* don't wait i.e. fail immediately */

                        break;

                    case ((ULONG)-1): /* wait indefinitely                */

                        ntStatus = SrvRegisterPendingLockState(
                                        pExecContext,
                                        pCtxSmb1->pTree,
                                        pBRLStateList,
                                        pLockState);
                        BAIL_ON_NT_STATUS(ntStatus);

                        break;

                    default:

                        {
                            LONG64 llExpiry = 0LL;

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

                            llExpiry +=
                                (pRequestHeader->ulTimeout *
                                 WIRE_FACTOR_MILLISECS_TO_HUNDREDS_OF_NANOSECS);

                            ntStatus = SrvRegisterPendingLockState(
                                            pExecContext,
                                            pCtxSmb1->pTree,
                                            pBRLStateList,
                                            pLockState);
                            BAIL_ON_NT_STATUS(ntStatus);

                            ntStatus = SrvTimerPostRequest(
                                            llExpiry,
                                            pExecContext,
                                            &SrvLockExpiredCB,
                                            &pLockState->pTimerRequest);
                            BAIL_ON_NT_STATUS(ntStatus);

                            SrvAcquireExecContext(pExecContext);
                        }

                        break;
                }

                //
                // Check for cancel a final time *after* adding the async state
                // so that a cancel is caught at least here or if async state
                // is cancelled.
                //
                if (SrvMpxTrackerIsCancelledExecContext(pExecContext))
                {
                    ntStatus = STATUS_CANCELLED;
                    BAIL_ON_NT_STATUS(ntStatus);
                }

                pLockState->stage = SRV_LOCK_STAGE_SMB_V1_ATTEMPT_LOCK;
            }

            // Intentional fall through

        case SRV_LOCK_STAGE_SMB_V1_ATTEMPT_LOCK:

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

            if (pRequestHeader->ucLockType & LWIO_LOCK_TYPE_CANCEL_LOCK)
            {
                ntStatus = SrvExecuteLockCancellation(pExecContext);
            }
            else
            {
                ntStatus = SrvExecuteLockRequest(pExecContext);
            }
            BAIL_ON_NT_STATUS(ntStatus);

            pLockState->stage = SRV_LOCK_STAGE_SMB_V1_DONE;

            // Intentional fall through

        case SRV_LOCK_STAGE_SMB_V1_DONE:

            if (!(pRequestHeader->ucLockType & LWIO_LOCK_TYPE_CANCEL_LOCK))
            {
                SrvClearLockAsyncState_inlock(pCtxSmb1->pTree, pLockState);
            }

            SrvFreeLockTimerRequest_inlock(pLockState);

            ntStatus = SrvBuildLockingAndXResponse(pExecContext);
            BAIL_ON_NT_STATUS(ntStatus);

            break;
    }

cleanup:

    if (pFile)
    {
        SrvFileRelease(pFile);
    }

    if (pTree)
    {
        SrvTreeRelease(pTree);
    }

    if (pSession)
    {
        SrvSessionRelease(pSession);
    }

    if (pOplockState)
    {
        SrvReleaseOplock(pOplockState);
    }

    return ntStatus;

error:

    switch (ntStatus)
    {
        case STATUS_PENDING:

            // TODO: Add an indicator to the file object to trigger a
            //       cleanup if the connection gets closed and all the
            //       files involved have to be closed

            break;

        case STATUS_CANCELLED:

            ntStatus = STATUS_FILE_LOCK_CONFLICT;

            // intentional fall through

        case STATUS_LOCK_NOT_GRANTED:

            if (pLockState)
            {
                if (pRequestHeader->ulTimeout != 0)
                {
                    ntStatus = STATUS_FILE_LOCK_CONFLICT;
                }

                SrvFileSetLastFailedLockOffset(
                    pLockState->pFile,
                    pLockState->Offset);
            }

            // intentional fall through

        default:

            if (pLockState)
            {
                SrvClearLockAsyncState_inlock(pCtxSmb1->pTree, pLockState);

                SrvFreeLockTimerRequest_inlock(pLockState);

                SrvReleaseExecContextAsync(pExecContext);

                SrvClearLocks(pLockState);
            }

            break;
    }

    goto cleanup;
}

static
NTSTATUS
SrvBuildLockState(
    PSMB_LOCKING_ANDX_REQUEST_HEADER pRequestHeader,
    PLOCKING_ANDX_RANGE_LARGE_FILE   pUnlockRangeLarge,
    PLOCKING_ANDX_RANGE_LARGE_FILE   pLockRangeLarge,
    PLOCKING_ANDX_RANGE              pUnlockRange,
    PLOCKING_ANDX_RANGE              pLockRange,
    PLWIO_SRV_FILE                   pFile,
    ULONG                            ulPid,
    USHORT                           usMid,
    PSRV_LOCK_STATE_SMB_V1*          ppLockState
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PSRV_LOCK_STATE_SMB_V1 pLockState = NULL;

    if (!(pRequestHeader->ucLockType & LWIO_LOCK_TYPE_OPLOCK_RELEASE))
    {
        if (pRequestHeader->ucLockType & LWIO_LOCK_TYPE_LARGE_FILES)
        {
            if ((pRequestHeader->usNumUnlocks && !pUnlockRangeLarge) ||
                (pRequestHeader->usNumLocks && !pLockRangeLarge))
            {
                ntStatus = STATUS_INVALID_NETWORK_RESPONSE;
            }
        }
        else
        {
            if ((pRequestHeader->usNumUnlocks && !pUnlockRange) ||
                (pRequestHeader->usNumLocks && !pLockRange))
            {
                ntStatus = STATUS_INVALID_NETWORK_RESPONSE;
            }
        }
        BAIL_ON_NT_STATUS(ntStatus);
    }

    ntStatus = SrvAllocateMemory(
                        sizeof(SRV_LOCK_STATE_SMB_V1),
                        OUT_PPVOID(&pLockState));
    BAIL_ON_NT_STATUS(ntStatus);

    pLockState->stage = SRV_LOCK_STAGE_SMB_V1_INITIAL;

    pLockState->ulPid      = ulPid;
    pLockState->usMid      = usMid;
    pLockState->ullAsyncId = 0;

    pLockState->pRequestHeader    = pRequestHeader;
    pLockState->pUnlockRangeLarge = pUnlockRangeLarge;
    pLockState->pLockRangeLarge   = pLockRangeLarge;
    pLockState->pUnlockRange      = pUnlockRange;
    pLockState->pLockRange        = pLockRange;

    pLockState->bRequestExclusiveLock =
     !(pLockState->pRequestHeader->ucLockType & LWIO_LOCK_TYPE_SHARED_LOCK);

    pLockState->pFile = SrvFileAcquire(pFile);

    pLockState->bExpired   = FALSE;
    pLockState->bCompleted = FALSE;

    LWIO_LOG_DEBUG("Created lock state [0x%08x]", pLockState);

    *ppLockState = pLockState;

cleanup:

    return ntStatus;

error:

    *ppLockState = NULL;

    if (pLockState)
    {
        SrvFreeLockState(pLockState);
    }

    goto cleanup;
}

static
NTSTATUS
SrvRegisterPendingLockState(
    PSRV_EXEC_CONTEXT               pExecContext,
    PLWIO_SRV_TREE                  pTree,
    PSRV_BYTE_RANGE_LOCK_STATE_LIST pBRLStateList,
    PSRV_LOCK_STATE_SMB_V1          pLockState
    )
{
    NTSTATUS ntStatus   = STATUS_SUCCESS;
    BOOLEAN  bInLock    = FALSE;
    PSRV_BYTE_RANGE_LOCK_STATE pBRLState   = NULL;
    PLWIO_ASYNC_STATE          pAsyncState = NULL;

    ntStatus = SrvAllocateMemory(
                    sizeof(SRV_BYTE_RANGE_LOCK_STATE),
                    OUT_PPVOID(&pBRLState));
    BAIL_ON_NT_STATUS(ntStatus);

    pBRLState->ullAsyncId = SrvAsyncStateBuildId(
                                pLockState->ulPid,
                                pLockState->usMid);

    ntStatus = SrvAsyncStateCreate(
                    pBRLState->ullAsyncId,
                    COM_LOCKING_ANDX,
                    pExecContext,
                    &SrvCancelLockState,
                    &SrvReleaseExecContextHandle,
                    &pAsyncState);
    BAIL_ON_NT_STATUS(ntStatus);

    SrvAcquireExecContext(pExecContext);

    pLockState->ullAsyncId = pBRLState->ullAsyncId; // publish for cancellation

    ntStatus = SrvTreeAddAsyncState(pTree, pAsyncState);
    BAIL_ON_NT_STATUS(ntStatus);

    LWIO_LOCK_MUTEX(bInLock, &pBRLStateList->mutex);

    if (!pBRLStateList->pHead)
    {
        pBRLStateList->pHead = pBRLStateList->pTail = pBRLState;
    }
    else
    {
        pBRLStateList->pTail->pNext = pBRLState;
        pBRLStateList->pTail = pBRLState;
    }

    pBRLState = NULL;

    LWIO_UNLOCK_MUTEX(bInLock, &pBRLStateList->mutex);

cleanup:

    if (pAsyncState)
    {
        SrvAsyncStateRelease(pAsyncState);
    }

    return ntStatus;

error:

    if (pBRLState)
    {
        SrvFreeMemory(pBRLState);
    }

    if (pLockState)
    {
        pLockState->ullAsyncId = 0;
    }

    goto cleanup;
}

static
BOOLEAN
SrvMatchesPendingLargeUnlockState(
    HANDLE                  hMatchArg,
    PSRV_LOCK_STATE_SMB_V1  pLockState,
    PUSHORT                 pusLockIndex
    )
{
    PLOCKING_ANDX_RANGE_LARGE_FILE pCandidateRange = hMatchArg;
    BOOLEAN                        bResult         = FALSE;
    USHORT                         usLockIndex     = 0;

    if (pLockState->pUnlockRangeLarge)
    {
        USHORT iLock            = 0;
        USHORT iNumUnlocks      = pLockState->pRequestHeader->usNumUnlocks;
        ULONG64 candidateOffset = 0;
        ULONG64 candidateLength = 0;

        candidateOffset = (((LONG64)pCandidateRange->ulOffsetHigh) << 32) |
                           ((LONG64)pCandidateRange->ulOffsetLow);

        candidateLength = (((LONG64)pCandidateRange->ulLengthHigh) << 32) |
                           ((LONG64)pCandidateRange->ulLengthLow);

        for (iLock = 0; iLock < iNumUnlocks; iLock++)
        {
            PLOCKING_ANDX_RANGE_LARGE_FILE pLockInfo =
                &pLockState->pUnlockRangeLarge[iLock];
            ULONG64 offset = 0;
            ULONG64 length = 0;

            offset = (((LONG64)pLockInfo->ulOffsetHigh) << 32) |
                      ((LONG64)pLockInfo->ulOffsetLow);

            length = (((LONG64)pLockInfo->ulLengthHigh) << 32) |
                      ((LONG64)pLockInfo->ulLengthLow);

            if ((pCandidateRange->usPid == pLockInfo->usPid) &&
                (candidateOffset == offset) &&
                (candidateLength == length))
            {
                usLockIndex = iLock;
                bResult = TRUE;
                break;
            }
        }
    }

    *pusLockIndex = usLockIndex;

    return bResult;
}

static
BOOLEAN
SrvMatchesPendingLargeLockState(
    HANDLE                  hMatchArg,
    PSRV_LOCK_STATE_SMB_V1  pLockState,
    PUSHORT                 pusLockIndex
    )
{
    PLOCKING_ANDX_RANGE_LARGE_FILE pCandidateRange = hMatchArg;
    BOOLEAN bResult     = FALSE;
    USHORT  usLockIndex = 0;

    if (pLockState->pLockRangeLarge)
    {
        USHORT iLock            = 0;
        USHORT iNumLocks        = pLockState->pRequestHeader->usNumLocks;
        ULONG64 candidateOffset = 0;
        ULONG64 candidateLength = 0;

        candidateOffset = (((LONG64)pCandidateRange->ulOffsetHigh) << 32) |
                           ((LONG64)pCandidateRange->ulOffsetLow);

        candidateLength = (((LONG64)pCandidateRange->ulLengthHigh) << 32) |
                           ((LONG64)pCandidateRange->ulLengthLow);

        for (iLock = 0; iLock < iNumLocks; iLock++)
        {
            PLOCKING_ANDX_RANGE_LARGE_FILE pLockInfo =
                &pLockState->pLockRangeLarge[iLock];
            ULONG64 offset = 0;
            ULONG64 length = 0;

            offset = (((LONG64)pLockInfo->ulOffsetHigh) << 32) |
                      ((LONG64)pLockInfo->ulOffsetLow);

            length = (((LONG64)pLockInfo->ulLengthHigh) << 32) |
                      ((LONG64)pLockInfo->ulLengthLow);

            if ((pCandidateRange->usPid == pLockInfo->usPid) &&
                (candidateOffset == offset) &&
                (candidateLength == length))
            {
                usLockIndex = iLock;
                bResult = TRUE;
                break;
            }
        }
    }

    *pusLockIndex = usLockIndex;

    return bResult;
}

static
BOOLEAN
SrvMatchesPendingUnlockState(
    HANDLE                 hMatchArg,
    PSRV_LOCK_STATE_SMB_V1 pLockState,
    PUSHORT                pusLockIndex
    )
{
    PLOCKING_ANDX_RANGE pCandidateRange = hMatchArg;
    BOOLEAN             bResult         = FALSE;
    USHORT              usLockIndex     = 0;

    if (pLockState->pUnlockRange)
    {
        USHORT iLock       = 0;
        USHORT iNumUnlocks = pLockState->pRequestHeader->usNumUnlocks;

        for (iLock = 0; iLock < iNumUnlocks; iLock++)
        {
            PLOCKING_ANDX_RANGE pLockInfo =
                &pLockState->pUnlockRange[iLock];

            if ((pCandidateRange->usPid == pLockInfo->usPid) &&
                (pCandidateRange->ulOffset == pLockInfo->ulOffset) &&
                (pCandidateRange->ulLength == pLockInfo->ulLength))
            {
                usLockIndex = iLock;
                bResult = TRUE;
                break;
            }
        }
    }

    *pusLockIndex = usLockIndex;

    return bResult;
}

static
BOOLEAN
SrvMatchesPendingLockState(
    HANDLE                 hMatchArg,
    PSRV_LOCK_STATE_SMB_V1 pLockState,
    PUSHORT                pusLockIndex
    )
{
    PLOCKING_ANDX_RANGE pCandidateRange = hMatchArg;
    BOOLEAN             bResult         = FALSE;
    USHORT              usLockIndex     = 0;

    if (pLockState->pLockRange)
    {
        USHORT iLock     = 0;
        USHORT iNumLocks = pLockState->pRequestHeader->usNumLocks;

        for (iLock = 0; iLock < iNumLocks; iLock++)
        {
            PLOCKING_ANDX_RANGE pLockInfo =
                        &pLockState->pLockRange[iLock];

            if ((pCandidateRange->usPid == pLockInfo->usPid) &&
                (pCandidateRange->ulOffset == pLockInfo->ulOffset) &&
                (pCandidateRange->ulLength == pLockInfo->ulLength))
            {
                usLockIndex = iLock;
                bResult = TRUE;
                break;
            }
        }
    }

    *pusLockIndex = usLockIndex;

    return bResult;
}

static
NTSTATUS
SrvFindLockState(
    PLOCK_MATCH_FUNC                pMatchFunc,
    HANDLE                          hMatchArg,
    PLWIO_SRV_TREE                  pTree,
    PSRV_BYTE_RANGE_LOCK_STATE_LIST pBRLStateList,
    PSRV_EXEC_CONTEXT*              ppExecContext,
    PSRV_LOCK_STATE_SMB_V1*         ppLockState,
    PUSHORT                         pusLockIndex
    )
{
    NTSTATUS                   ntStatus              = STATUS_SUCCESS;
    BOOLEAN                    bInLock               = FALSE;
    USHORT                     usLockIdx             = 0;
    PLWIO_ASYNC_STATE          pAsyncState           = NULL;
    PSRV_BYTE_RANGE_LOCK_STATE pBRLStateCursor       = NULL;
    PSRV_EXEC_CONTEXT          pCandidateExecContext = NULL;
    PSRV_LOCK_STATE_SMB_V1     pCandidateLockState   = NULL;

    LWIO_LOCK_MUTEX(bInLock, &pBRLStateList->mutex);

    for (pBRLStateCursor = pBRLStateList->pHead;
         pBRLStateCursor != NULL;
         pBRLStateCursor = pBRLStateCursor->pNext)
    {
        PSRV_EXEC_CONTEXT pExecContext    = NULL;
        PSRV_EXEC_CONTEXT_SMB_V1 pCtxSmb1 = NULL;
        PSRV_LOCK_STATE_SMB_V1 pLockState = NULL;

        if (pAsyncState)
        {
            SrvAsyncStateRelease(pAsyncState);
            pAsyncState = NULL;
        }

        ntStatus = SrvTreeFindAsyncState(
                        pTree,
                        pBRLStateCursor->ullAsyncId,
                        &pAsyncState);
        if (ntStatus == STATUS_NOT_FOUND)
        {
            ntStatus = STATUS_SUCCESS;
            continue;
        }
        BAIL_ON_NT_STATUS(ntStatus);

        pExecContext = pAsyncState->hAsyncState;
        pCtxSmb1     = pExecContext->pProtocolContext;
        pLockState   = pCtxSmb1->hState;

        if (pMatchFunc(hMatchArg, pLockState, &usLockIdx))
        {
            pCandidateExecContext = SrvAcquireExecContext(pExecContext);
            pCandidateLockState = pLockState;

            break;
        }
    }

    if (!pCandidateLockState)
    {
        ntStatus = STATUS_NOT_FOUND;
        BAIL_ON_NT_STATUS(ntStatus);
    }

    *ppExecContext = pCandidateExecContext;
    *ppLockState  = pCandidateLockState;
    *pusLockIndex = usLockIdx;

cleanup:

    if (pAsyncState)
    {
        SrvAsyncStateRelease(pAsyncState);
    }

    LWIO_UNLOCK_MUTEX(bInLock, &pBRLStateList->mutex);

    return ntStatus;

error:

    *ppExecContext = NULL;
    *ppLockState   = NULL;
    *pusLockIndex  = 0;

    goto cleanup;
}

static
NTSTATUS
SrvUnregisterBRLState(
    PSRV_BYTE_RANGE_LOCK_STATE_LIST pBRLStateList,
    PSRV_LOCK_STATE_SMB_V1          pLockState
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    BOOLEAN  bInLock  = FALSE;
    PSRV_BYTE_RANGE_LOCK_STATE pCursor = NULL;
    PSRV_BYTE_RANGE_LOCK_STATE pPrev   = NULL;

    LWIO_LOCK_MUTEX(bInLock, &pBRLStateList->mutex);

    pCursor = pBRLStateList->pHead;
    while (pCursor && (pCursor->ullAsyncId != pLockState->ullAsyncId))
    {
        pPrev   = pCursor;
        pCursor = pCursor->pNext;
    }

    if (!pCursor)
    {
        ntStatus = STATUS_NOT_FOUND;
        BAIL_ON_NT_STATUS(ntStatus);
    }

    if (pCursor == pBRLStateList->pHead)
    {
        if (pBRLStateList->pHead == pBRLStateList->pTail)
        {
            pBRLStateList->pTail = NULL;
        }

        pBRLStateList->pHead = pBRLStateList->pHead->pNext;
    }
    else if (pCursor == pBRLStateList->pTail)
    {
        pBRLStateList->pTail = pPrev;
        pPrev->pNext = NULL;
    }
    else
    {
        pPrev->pNext = pCursor->pNext;
    }

    pCursor->pNext = NULL;

    SrvFreeMemory(pCursor);

cleanup:

    LWIO_UNLOCK_MUTEX(bInLock, &pBRLStateList->mutex);

    return ntStatus;

error:

    goto cleanup;
}

static
NTSTATUS
SrvExecuteLockCancellation(
    PSRV_EXEC_CONTEXT pExecContext
    )
{
    NTSTATUS                   ntStatus     = STATUS_SUCCESS;
    PSRV_EXEC_CONTEXT_SMB_V1   pCtxSmb1     = pExecContext->pProtocolContext;
    PSRV_LOCK_STATE_SMB_V1     pLockState   = pCtxSmb1->hState;
    USHORT                     usLockIndex  = 0;
    BOOLEAN                    bInLock      = FALSE;
    PSRV_BYTE_RANGE_LOCK_STATE_LIST pBRLStateList = NULL;
    PSRV_EXEC_CONTEXT          pExecContextToCancel = NULL;
    PSRV_LOCK_STATE_SMB_V1     pLockStateToCancel   = NULL;

    pBRLStateList = pCtxSmb1->pFile->hCancellableBRLStateList;

    if (pLockState->pRequestHeader->ucLockType & LWIO_LOCK_TYPE_LARGE_FILES)
    {
        if (pLockState->pUnlockRangeLarge)
        {
            ntStatus = SrvFindLockState(
                            SrvMatchesPendingLargeUnlockState,
                            &pLockState->pUnlockRangeLarge[0],
                            pCtxSmb1->pTree,
                            pBRLStateList,
                            &pExecContextToCancel,
                            &pLockStateToCancel,
                            &usLockIndex);
        }
        else if (pLockState->pLockRangeLarge)
        {
            ntStatus = SrvFindLockState(
                            SrvMatchesPendingLargeLockState,
                            &pLockState->pLockRangeLarge[0],
                            pCtxSmb1->pTree,
                            pBRLStateList,
                            &pExecContextToCancel,
                            &pLockStateToCancel,
                            &usLockIndex);
        }
        else
        {
            ntStatus = STATUS_INVALID_PARAMETER;
        }
    }
    else
    {
        if (pLockState->pUnlockRange)
        {
            ntStatus = SrvFindLockState(
                            SrvMatchesPendingUnlockState,
                            &pLockState->pUnlockRange[0],
                            pCtxSmb1->pTree,
                            pBRLStateList,
                            &pExecContextToCancel,
                            &pLockStateToCancel,
                            &usLockIndex);
        }
        else if (pLockState->pLockRange)
        {
            ntStatus = SrvFindLockState(
                            SrvMatchesPendingLockState,
                            &pLockState->pLockRange[0],
                            pCtxSmb1->pTree,
                            pBRLStateList,
                            &pExecContextToCancel,
                            &pLockStateToCancel,
                            &usLockIndex);
        }
        else
        {
            ntStatus = STATUS_INVALID_PARAMETER;
        }
    }
    BAIL_ON_NT_STATUS(ntStatus);

    SrvTreeRemoveAsyncState(pCtxSmb1->pTree, pLockStateToCancel->ullAsyncId);

    ntStatus = SrvUnregisterBRLState(pBRLStateList, pLockStateToCancel);
    BAIL_ON_NT_STATUS(ntStatus);

    LWIO_LOCK_MUTEX(bInLock, &pExecContextToCancel->execMutex);

    SrvFreeLockTimerRequest_inlock(pLockStateToCancel);

    if (pLockStateToCancel->bExpired)
    {
        ntStatus = STATUS_NOT_FOUND;
        BAIL_ON_NT_STATUS(ntStatus);
    }

    if (!pLockStateToCancel->bCompleted)
    {
        SrvCancelLockState_inlock(pExecContextToCancel, pLockStateToCancel);
    }
    else
    {
        ntStatus = STATUS_NOT_FOUND;
        BAIL_ON_NT_STATUS(ntStatus);
    }

cleanup:

    if (pLockStateToCancel)
    {
        LWIO_UNLOCK_MUTEX(bInLock, &pExecContext->execMutex);

        SrvReleaseExecContext(pExecContextToCancel);
    }

    return ntStatus;

error:

    switch (ntStatus)
    {
        case STATUS_NOT_FOUND:

            ntStatus = ERROR_CANCEL_VIOLATION;

            break;

        default:

            break;
    }

    goto cleanup;
}

VOID
SrvCancelLockState(
    HANDLE hContext
    )
{
    PSRV_EXEC_CONTEXT          pExecContext = hContext;
    PSRV_EXEC_CONTEXT_SMB_V1   pCtxSmb1     = pExecContext->pProtocolContext;
    PSRV_LOCK_STATE_SMB_V1     pLockState   = pCtxSmb1->hState;
    BOOLEAN                    bInLock      = FALSE;

    LWIO_LOCK_MUTEX(bInLock, &pExecContext->execMutex);

    SrvCancelLockState_inlock(pExecContext, pLockState);

    SrvFreeLockTimerRequest_inlock(pLockState);

    LWIO_UNLOCK_MUTEX(bInLock, &pExecContext->execMutex);
}

static
VOID
SrvCancelLockState_inlock(
    PSRV_EXEC_CONTEXT pExecContext,
    PSRV_LOCK_STATE_SMB_V1 pLockState
    )
{
    pLockState->bCancelled = TRUE;

    SrvCancelExecContext_inlock(pExecContext);
}

static
VOID
SrvFreeLockTimerRequest_inlock(
    PSRV_LOCK_STATE_SMB_V1 pLockState
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;

    if (pLockState->pTimerRequest)
    {
        PSRV_EXEC_CONTEXT pExecContext = NULL;

        ntStatus = SrvTimerCancelRequest(
                        pLockState->pTimerRequest,
                        OUT_PPVOID(&pExecContext));

        if (ntStatus == STATUS_SUCCESS)
        {
            SrvReleaseExecContext(pExecContext);
        }
        SrvTimerRelease(&pLockState->pTimerRequest);
    }
}

static
VOID
SrvClearLockAsyncState_inlock(
    PLWIO_SRV_TREE         pTree,
    PSRV_LOCK_STATE_SMB_V1 pLockState
    )
{
    if (pLockState->ullAsyncId)
    {
        PLWIO_SRV_FILE pFile     = pLockState->pFile;

        PSRV_BYTE_RANGE_LOCK_STATE_LIST pBRLStateList =
            (PSRV_BYTE_RANGE_LOCK_STATE_LIST)pFile->hCancellableBRLStateList;

        SrvTreeRemoveAsyncState(pTree, pLockState->ullAsyncId);

        pLockState->ullAsyncId = 0;
        (void)SrvUnregisterBRLState(pBRLStateList, pLockState);
    }
}

static
NTSTATUS
SrvExecuteLockRequest(
    PSRV_EXEC_CONTEXT pExecContext
    )
{
    NTSTATUS                   ntStatus     = STATUS_SUCCESS;
    PSRV_EXEC_CONTEXT_SMB_V1   pCtxSmb1     = pExecContext->pProtocolContext;
    PSRV_LOCK_STATE_SMB_V1     pLockState   = pCtxSmb1->hState;
    BOOLEAN                    bFailImmediately  = FALSE;

    bFailImmediately  = (pLockState->pRequestHeader->ulTimeout == 0);

    if (pLockState->bUnlockPending)
    {
        ntStatus = pExecContext->ioStatusBlock.Status; // async response status
        BAIL_ON_NT_STATUS(ntStatus);

        pLockState->iUnlock++;
        pLockState->bUnlockPending = FALSE;

        SrvFileRegisterUnlock(pLockState->pFile);
    }

    if (pLockState->bLockPending)
    {
        ntStatus = pExecContext->ioStatusBlock.Status; // async response status
        BAIL_ON_NT_STATUS(ntStatus);

        pLockState->iLock++;
        pLockState->bLockPending = FALSE;

        SrvFileRegisterLock(pLockState->pFile);
    }

    if (pLockState->bCancelled || pLockState->bExpired)
    {
        SrvReleaseExecContextAsync(pExecContext);

        ntStatus = STATUS_FILE_LOCK_CONFLICT;
        BAIL_ON_NT_STATUS(ntStatus);
    }

    for (; pLockState->iUnlock < pLockState->pRequestHeader->usNumUnlocks;
           pLockState->iUnlock++)
    {
        if (pLockState->pRequestHeader->ucLockType & LWIO_LOCK_TYPE_LARGE_FILES)
        {
            PLOCKING_ANDX_RANGE_LARGE_FILE pLockInfo =
                        &pLockState->pUnlockRangeLarge[pLockState->iUnlock];

            pLockState->Offset = (((LONG64)pLockInfo->ulOffsetHigh) << 32) |
                                  ((LONG64)pLockInfo->ulOffsetLow);

            pLockState->Length = (((LONG64)pLockInfo->ulLengthHigh) << 32) |
                                  ((LONG64)pLockInfo->ulLengthLow);

            pLockState->Key    = pLockInfo->usPid;
        }
        else
        {
            PLOCKING_ANDX_RANGE pLockInfo =
                        &pLockState->pUnlockRange[pLockState->iUnlock];

            pLockState->Offset = pLockInfo->ulOffset;
            pLockState->Length = pLockInfo->ulLength;
            pLockState->Key    = pLockInfo->usPid;
        }

        SrvPrepareExecContextAsync(pExecContext);

        ntStatus = IoUnlockFile(
                        pLockState->pFile->hFile,
                        pExecContext->pAcb,
                        &pExecContext->ioStatusBlock,
                        pLockState->Offset,
                        pLockState->Length,
                        pLockState->Key);
        if (ntStatus == STATUS_PENDING)
        {
            pLockState->bUnlockPending = TRUE;
        }
        BAIL_ON_NT_STATUS(ntStatus);

        SrvFileRegisterUnlock(pLockState->pFile);

        SrvReleaseExecContextAsync(pExecContext); // completed synchronously
    }

    for (; pLockState->iLock < pLockState->pRequestHeader->usNumLocks;
           pLockState->iLock++)
    {
        if (pLockState->pRequestHeader->ucLockType & LWIO_LOCK_TYPE_LARGE_FILES)
        {
            PLOCKING_ANDX_RANGE_LARGE_FILE pLockInfo =
                        &pLockState->pLockRangeLarge[pLockState->iLock];

            pLockState->Offset = (((LONG64)pLockInfo->ulOffsetHigh) << 32) |
                                   ((LONG64)pLockInfo->ulOffsetLow);

            pLockState->Length = (((LONG64)pLockInfo->ulLengthHigh) << 32) |
                                   ((LONG64)pLockInfo->ulLengthLow);

            pLockState->Key    = pLockInfo->usPid;
        }
        else
        {
            PLOCKING_ANDX_RANGE pLockInfo =
                        &pLockState->pLockRange[pLockState->iLock];

            pLockState->Offset = pLockInfo->ulOffset;
            pLockState->Length = pLockInfo->ulLength;
            pLockState->Key    = pLockInfo->usPid;
        }

        if (!bFailImmediately)
        {
            SrvPrepareExecContextAsync(pExecContext);
        }

        ntStatus = IoLockFile(
                        pLockState->pFile->hFile,
                        pExecContext->pAcb,
                        &pExecContext->ioStatusBlock,
                        pLockState->Offset,
                        pLockState->Length,
                        pLockState->Key,
                        bFailImmediately,
                        pLockState->bRequestExclusiveLock);
        if (ntStatus == STATUS_PENDING)
        {
            pLockState->bLockPending = TRUE;
        }
        BAIL_ON_NT_STATUS(ntStatus);

        SrvFileRegisterLock(pLockState->pFile);

        if (!bFailImmediately)
        {
            SrvReleaseExecContextAsync(pExecContext); // completed synchronously
        }
    }

    pLockState->bCompleted = TRUE;

cleanup:

    return ntStatus;

error:

    goto cleanup;
}

static
VOID
SrvLockExpiredCB(
    PSRV_TIMER_REQUEST pTimerRequest,
    PVOID              pUserData
    )
{
    PSRV_EXEC_CONTEXT          pExecContext = pUserData;
    PSRV_EXEC_CONTEXT_SMB_V1   pCtxSmb1     = pExecContext->pProtocolContext;
    PSRV_LOCK_STATE_SMB_V1     pLockState   = pCtxSmb1->hState;
    BOOLEAN                    bInLock      = FALSE;

    LWIO_LOCK_MUTEX(bInLock, &pExecContext->execMutex);

    if (pLockState->pTimerRequest == pTimerRequest)
    {
        pLockState->bExpired = TRUE;
        SrvCancelLockState_inlock(pExecContext, pLockState);
        SrvTimerRelease(&pLockState->pTimerRequest);
    }

    LWIO_UNLOCK_MUTEX(bInLock, &pExecContext->execMutex);

    SrvReleaseExecContext(pExecContext);
}

static
VOID
SrvClearLocks(
    PSRV_LOCK_STATE_SMB_V1 pLockState
    )
{
    // Unlock in reverse order
    while (pLockState->iLock-- > 0)
    {
        NTSTATUS        ntStatus      = STATUS_SUCCESS;
        IO_STATUS_BLOCK ioStatusBlock = {0};
        ULONG64         Offset        = 0;
        ULONG64         Length        = 0;
        ULONG           Key           = 0;

        if (pLockState->pRequestHeader->ucLockType & LWIO_LOCK_TYPE_LARGE_FILES)
        {
            PLOCKING_ANDX_RANGE_LARGE_FILE pLockInfo =
                        &pLockState->pLockRangeLarge[pLockState->iLock];

            Offset = (((LONG64)pLockInfo->ulOffsetHigh) << 32) |
                      ((LONG64)pLockInfo->ulOffsetLow);

            Length = (((LONG64)pLockInfo->ulLengthHigh) << 32) |
                      ((LONG64)pLockInfo->ulLengthLow);

            Key    = pLockInfo->usPid;
        }
        else
        {
            PLOCKING_ANDX_RANGE pLockInfo =
                        &pLockState->pLockRange[pLockState->iLock];

            Offset = pLockInfo->ulOffset;
            Length = pLockInfo->ulLength;
            Key    = pLockInfo->usPid;
        }

        ntStatus = IoUnlockFile(
                        pLockState->pFile->hFile,
                        NULL,
                        &ioStatusBlock,
                        Offset,
                        Length,
                        Key);
        if (ntStatus != STATUS_SUCCESS)
        {
            LWIO_LOG_ERROR("Failed to unlock range "
                           "[fid:%d; offset:%uld; length:%uld][status:0x%x]",
                            pLockState->pFile->fid,
                            Offset,
                            Length,
                            ntStatus);
        }
        else
        {
            SrvFileRegisterUnlock(pLockState->pFile);
        }
    }
}

static
NTSTATUS
SrvBuildLockingAndXResponse(
    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];
    PSRV_MESSAGE_SMB_V1        pSmbResponse = &pCtxSmb1->pResponses[iMsg];
    PSMB_LOCKING_ANDX_RESPONSE_HEADER pResponseHeader = NULL; // Do not free
    PBYTE pOutBuffer           = pSmbResponse->pBuffer;
    ULONG ulBytesAvailable     = pSmbResponse->ulBytesAvailable;
    ULONG ulOffset             = 0;
    USHORT usBytesUsed         = 0;
    ULONG ulTotalBytesUsed     = 0;

    if (!pSmbResponse->ulSerialNum)
    {
        ntStatus = SrvMarshalHeader_SMB_V1(
                        pOutBuffer,
                        ulOffset,
                        ulBytesAvailable,
                        COM_LOCKING_ANDX,
                        STATUS_SUCCESS,
                        TRUE,
                        (pSmbRequest->pHeader->flags2 & FLAG2_ERR_STATUS) != 0,
                        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 = 2;

    ntStatus = WireMarshallLockingAndXResponse(
                    pOutBuffer,
                    ulBytesAvailable,
                    ulOffset,
                    &pResponseHeader,
                    &usBytesUsed);
    BAIL_ON_NT_STATUS(ntStatus);

    pResponseHeader->usByteCount = 0;

    // pOutBuffer       += usBytesUsed;
    // ulOffset         += usBytesUsed;
    // ulBytesAvailable -= usBytesUsed;
    ulTotalBytesUsed += usBytesUsed;

    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
SrvFreeLockStateHandle(
    HANDLE hState
    )
{
    SrvFreeLockState(hState);
}

static
VOID
SrvFreeLockState(
    PSRV_LOCK_STATE_SMB_V1 pLockState
    )
{
    LWIO_LOG_DEBUG("Freeing lock state [0x%08x]", pLockState);

    SrvFreeLockTimerRequest_inlock(pLockState);

    if (pLockState->pFile)
    {
        SrvFileRelease(pLockState->pFile);
    }

    SrvFreeMemory(pLockState);
}

static
VOID
SrvConvertLockTimeout(
    PSRV_LOCK_STATE_SMB_V1 pLockState
    )
{
        int i = 0;
        ULONG64 ullOffset = 0;
        const ULONG64 ullMaxNonBlockingLockOffset = 0xEF000000;
        ULONG64 ullLastFailedLockOffset = (ULONG64)-1;

        // If a requested lock has previously conflicted with a held lock
        // we save the offset. If it is requested again in a non-blocking
        // manner we instead give it a small timeout. This throttles clients
        // which are repeatedly polling for the same lock and overloading the
        // server. In addition locks over a certain offset, when the 64-bit is
        // not set, are always given a short timeout.

        if (pLockState->pRequestHeader->ulTimeout != 0)
        {
                goto cleanup;
        }

        ullLastFailedLockOffset =
                SrvFileGetLastFailedLockOffset(pLockState->pFile);

        for (i = 0; i < pLockState->pRequestHeader->usNumLocks; i++)
        {
            if (pLockState->pRequestHeader->ucLockType &
                LWIO_LOCK_TYPE_LARGE_FILES)
            {
                PLOCKING_ANDX_RANGE_LARGE_FILE pLockInfo =
                            &pLockState->pLockRangeLarge[i];

                ullOffset = (((ULONG64)pLockInfo->ulOffsetHigh) << 32) |
                            ((ULONG64)pLockInfo->ulOffsetLow);
            }
            else
            {
                PLOCKING_ANDX_RANGE pLockInfo = &pLockState->pLockRange[i];

                ullOffset = pLockInfo->ulOffset;
            }

            if (ullOffset == ullLastFailedLockOffset ||
                (ullOffset >= ullMaxNonBlockingLockOffset &&
                (ullOffset & 0x8000000000000000LL) == 0))

            {
                pLockState->pRequestHeader->ulTimeout =
                    SrvConfigGetLockConflictTimeoutMillisecs_SMB_V1();
                break;
            }
        }

cleanup:
        return;
}
