/* -*- 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:
 *
 *        scb.c
 *
 * Abstract:
 *
 *        Likewise Posix File System Driver (PVFS)
 *
 *        File Stream Control Block routines
 *
 * Authors: Gerald Carter <gcarter@likewise.com>
 */

#include "pvfs.h"

static
NTSTATUS
PvfsAddOplockOrLeaseRecord(
    IN OUT PPVFS_SCB pScb,
    IN     PPVFS_IRP_CONTEXT pIrpContext,
    IN     PPVFS_CCB pCcb,
    IN     IO_OPLOCK_TYPE OplockType,
    IN     IO_LEASE_STATE LeaseState
    );

static
VOID
PvfspOplockIncrementCounter(
    IN PPVFS_OPLOCK_RECORD OplockRecord
    );

static
VOID
PvfspOplockDecrementCounter(
    IN PPVFS_OPLOCK_RECORD OplockRecord
    );

/*****************************************************************************
 ****************************************************************************/

static
VOID
PvfsFreeSCB(
    PPVFS_SCB pScb
    )
{
    if (pScb)
    {
        LWIO_ASSERT(pScb->BaseControlBlock.RefCount == 0);

        if (pScb->pOwnerFcb)
        {
            PvfsReleaseFCB(&pScb->pOwnerFcb);
        }

        RtlCStringFree(&pScb->pszStreamname);

        pthread_rwlock_destroy(&pScb->rwCcbLock);
        pthread_rwlock_destroy(&pScb->rwBrlLock);

        PvfsListDestroy(&pScb->pPendingLockQueue);
        PvfsListDestroy(&pScb->pOplockPendingOpsQueue);
        PvfsListDestroy(&pScb->pOplockList);
        PvfsListDestroy(&pScb->pCcbList);

        PvfsDestroyCB(&pScb->BaseControlBlock);

        PVFS_FREE(&pScb);

        InterlockedDecrement(&gPvfsDriverState.Counters.Scb);
    }

    return;
}


/*****************************************************************************
 ****************************************************************************/

static
VOID
PvfsSCBFreeCCB(
    PVOID *ppData
    )
{
    /* This should never be called.  The CCB count has to be 0 when
       we call PvfsFreeSCB() and hence destroy the SCB->pCcbList */

    return;
}


/*****************************************************************************
 ****************************************************************************/

NTSTATUS
PvfsAllocateSCB(
    PPVFS_SCB *ppScb
    )
{
    NTSTATUS ntError = STATUS_UNSUCCESSFUL;
    PPVFS_SCB pScb = NULL;

    *ppScb = NULL;

    ntError = PvfsAllocateMemory(
                  OUT_PPVOID(&pScb),
                  sizeof(PVFS_SCB),
                  FALSE);
    BAIL_ON_NT_STATUS(ntError);

    PvfsInitializeCB(&pScb->BaseControlBlock);

    /* Initialize mutexes and refcounts */

    pthread_rwlock_init(&pScb->rwCcbLock, NULL);
    pthread_rwlock_init(&pScb->rwBrlLock, NULL);

    /* Setup pendlock byte-range lock queue */

    ntError = PvfsListInit(
                  &pScb->pPendingLockQueue,
                  PVFS_SCB_MAX_PENDING_LOCKS,
                  (PPVFS_LIST_FREE_DATA_FN)PvfsFreePendingLock);
    BAIL_ON_NT_STATUS(ntError);

    /* Oplock pending ops queue */

    ntError = PvfsListInit(
                  &pScb->pOplockPendingOpsQueue,
                  PVFS_SCB_MAX_PENDING_OPERATIONS,
                  (PPVFS_LIST_FREE_DATA_FN)PvfsFreePendingOp);
    BAIL_ON_NT_STATUS(ntError);

    /* Oplock list and state */

    pScb->bOplockBreakInProgress = FALSE;
    ntError = PvfsListInit(
                  &pScb->pOplockList,
                  0,
                  (PPVFS_LIST_FREE_DATA_FN)PvfsFreeOplockRecord);
    BAIL_ON_NT_STATUS(ntError);

    /* List of CCBs */

    ntError = PvfsListInit(
                  &pScb->pCcbList,
                  0,
                  (PPVFS_LIST_FREE_DATA_FN)PvfsSCBFreeCCB);
    BAIL_ON_NT_STATUS(ntError);

    /* Miscellaneous */

    PVFS_CLEAR_FILEID(pScb->FileId);

    pScb->AllocationSize = 0;
    pScb->OpenHandleCount = 0;
    pScb->bDeleteOnClose = FALSE;
    pScb->bOplockBreakInProgress = FALSE;
    pScb->pszStreamname = NULL;
    pScb->pOwnerFcb = NULL;

    pScb->CountOplockLevel2 = 0;
    pScb->CountOplockLevel1 = 0;
    pScb->CountOplockBatch = 0;
    pScb->CountLeaseR = 0;
    pScb->CountLeaseRH = 0;
    pScb->CountLeaseRW = 0;
    pScb->CountLeaseRWH = 0;

    *ppScb = pScb;

    InterlockedIncrement(&gPvfsDriverState.Counters.Scb);

    ntError = STATUS_SUCCESS;

cleanup:
    return ntError;

error:
    if (pScb)
    {
        PvfsFreeSCB(pScb);
    }

    goto cleanup;
}

/*******************************************************
 ******************************************************/

PPVFS_SCB
PvfsReferenceSCB(
    IN PPVFS_SCB pScb
    )
{
    InterlockedIncrement(&pScb->BaseControlBlock.RefCount);

    return pScb;
}

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

VOID
PvfsReleaseSCB(
    PPVFS_SCB *ppScb
    )
{
    BOOLEAN scbListLocked = FALSE;
    PPVFS_SCB pScb = NULL;
    PPVFS_FCB pFcb = NULL;
    LONG refCount = 0;

    LWIO_ASSERT((ppScb != NULL) &&  (*ppScb != NULL));

    pScb = *ppScb;

    // It is important to lock the ScbTable here so that there is no window
    // between the refcount check and the remove. Otherwise another open request
    // could search and locate the SCB in the tree and return free()'d memory.
    // However, if the SCB has no bucket pointer, it has already been removed
    // from the ScbTable so locking is unnecessary.

    pFcb = PvfsReferenceFCB(pScb->pOwnerFcb);

    LWIO_LOCK_RWMUTEX_EXCLUSIVE(scbListLocked, &pFcb->rwScbLock);

    refCount = InterlockedDecrement(&pScb->BaseControlBlock.RefCount);
    LWIO_ASSERT(refCount >= 0);

    if (refCount == 0)
    {
        LWIO_ASSERT(PvfsListIsEmpty(pScb->pCcbList));

        PvfsRemoveSCBFromFCB_inlock(pFcb, pScb);
        LWIO_UNLOCK_RWMUTEX(scbListLocked, &pFcb->rwScbLock);

        PvfsFreeSCB(pScb);
    }

    LWIO_UNLOCK_RWMUTEX(scbListLocked, &pFcb->rwScbLock);

    if (pFcb)
    {
        PvfsReleaseFCB(&pFcb);
    }

    *ppScb = NULL;

    return;
}

/*******************************************************
 ******************************************************/

NTSTATUS
PvfsAddCCBToSCB(
    PPVFS_SCB pScb,
    PPVFS_CCB pCcb
    )
{
    NTSTATUS ntError = STATUS_UNSUCCESSFUL;
    BOOLEAN scbWriteLocked = FALSE;
    BOOLEAN fcbWriteLocked = FALSE;

    LWIO_LOCK_RWMUTEX_EXCLUSIVE(scbWriteLocked, &pScb->rwCcbLock);
    LWIO_LOCK_RWMUTEX_EXCLUSIVE(fcbWriteLocked, &pScb->pOwnerFcb->rwScbLock);

    ntError = PvfsListAddTail(pScb->pCcbList, &pCcb->ScbList);
    BAIL_ON_NT_STATUS(ntError);

    pCcb->pScb = PvfsReferenceSCB(pScb);

    pScb->OpenHandleCount++;
    pScb->pOwnerFcb->OpenHandleCount++;

    LWIO_ASSERT((pScb->OpenHandleCount > 0) &&
                (pScb->pOwnerFcb->OpenHandleCount > 0));

    ntError = STATUS_SUCCESS;

cleanup:
    LWIO_UNLOCK_RWMUTEX(fcbWriteLocked, &pScb->pOwnerFcb->rwScbLock);
    LWIO_UNLOCK_RWMUTEX(scbWriteLocked, &pScb->rwCcbLock);

    return ntError;

error:
    goto cleanup;
}

/*******************************************************
 ******************************************************/

NTSTATUS
PvfsRemoveCCBFromSCB(
    PPVFS_SCB pScb,
    PPVFS_CCB pCcb
    )
{
    NTSTATUS ntError = STATUS_SUCCESS;
    BOOLEAN scbWriteLocked = FALSE;
    BOOLEAN fcbWriteLocked = FALSE;

    LWIO_LOCK_RWMUTEX_EXCLUSIVE(scbWriteLocked, &pScb->rwCcbLock);
    LWIO_LOCK_RWMUTEX_EXCLUSIVE(fcbWriteLocked, &pScb->pOwnerFcb->rwScbLock);

    ntError = PvfsListRemoveItem(pScb->pCcbList, &pCcb->ScbList);
    BAIL_ON_NT_STATUS(ntError);

    pScb->OpenHandleCount--;
    pScb->pOwnerFcb->OpenHandleCount--;

    LWIO_ASSERT((pScb->OpenHandleCount >= 0) &&
                (pScb->pOwnerFcb->OpenHandleCount >= 0));

    ntError = PvfsCloseHandleCleanup(pScb);
    // Ignore errors

    if (pScb->OpenHandleCount == 0)
    {
        // Clear and reset this from disk
        pScb->AllocationSize = 0;
    }


error:

    LWIO_UNLOCK_RWMUTEX(fcbWriteLocked, &pScb->pOwnerFcb->rwScbLock);
    LWIO_UNLOCK_RWMUTEX(scbWriteLocked, &pScb->rwCcbLock);


    return ntError;
}


/*****************************************************************************
 ****************************************************************************/

NTSTATUS
PvfsPendOplockBreakTest(
    IN PPVFS_SCB pScb,
    IN PPVFS_IRP_CONTEXT pIrpContext,
    IN PPVFS_CCB pCcb,
    IN PPVFS_OPLOCK_PENDING_COMPLETION_CALLBACK pfnCompletion,
    IN PPVFS_OPLOCK_PENDING_COMPLETION_FREE_CTX pfnFreeContext,
    IN PVOID pCompletionContext
    )
{
    NTSTATUS ntError = STATUS_UNSUCCESSFUL;
    PPVFS_PENDING_OPLOCK_BREAK_TEST pTestCtx = NULL;

    BAIL_ON_INVALID_PTR(pScb, ntError);
    BAIL_ON_INVALID_PTR(pfnCompletion, ntError);

    ntError = PvfsCreateOplockBreakTestContext(
                  &pTestCtx,
                  pScb,
                  pIrpContext,
                  pCcb,
                  pfnCompletion,
                  pfnFreeContext,
                  pCompletionContext);
    BAIL_ON_NT_STATUS(ntError);

    // Transfer owndership of pTestCtx to PvfsAddItemPendingOplockBreakAck().
    // It transitively owns pCompletionContext via pTestCtx.
    ntError = PvfsAddItemPendingOplockBreakAck(
                  pScb,
                  pIrpContext,
                  PvfsOplockPendingBreakIfLocked,
                  PvfsFreeOplockBreakTestContext,
                  pTestCtx);

cleanup:

    return ntError;

error:

    // Allocation failure.  Since we still own pCompletionContext,
    // we must free it.
    if (pfnFreeContext)
    {
        pfnFreeContext(&pCompletionContext);
    }
    else
    {
        PVFS_FREE(&pCompletionContext);
    }

    goto cleanup;
}

/*****************************************************************************
 ****************************************************************************/

NTSTATUS
PvfsAddItemPendingOplockBreakAck(
    IN PPVFS_SCB pScb,
    IN PPVFS_IRP_CONTEXT pIrpContext,
    IN PPVFS_OPLOCK_PENDING_COMPLETION_CALLBACK pfnCompletion,
    IN PPVFS_OPLOCK_PENDING_COMPLETION_FREE_CTX pfnFreeContext,
    IN PVOID pCompletionContext
    )
{
    NTSTATUS ntError = STATUS_UNSUCCESSFUL;
    BOOLEAN bLocked = FALSE;
    PPVFS_OPLOCK_PENDING_OPERATION pPendingOp = NULL;

    BAIL_ON_INVALID_PTR(pScb, ntError);
    BAIL_ON_INVALID_PTR(pfnCompletion, ntError);

    LWIO_LOCK_MUTEX(bLocked, &pScb->BaseControlBlock.Mutex);

    if (!pScb->bOplockBreakInProgress)
    {
        LWIO_UNLOCK_MUTEX(bLocked, &pScb->BaseControlBlock.Mutex);

        ntError = pfnCompletion(pCompletionContext);
        if (pfnFreeContext)
        {
            pfnFreeContext(&pCompletionContext);
        }
        else
        {
            PVFS_FREE(&pCompletionContext);
        }

        goto cleanup;
    }

    ntError = PvfsAllocateMemory(
                  OUT_PPVOID(&pPendingOp),
                  sizeof(PVFS_OPLOCK_PENDING_OPERATION),
                  FALSE);
    BAIL_ON_NT_STATUS(ntError);

    PVFS_INIT_LINKS(&pPendingOp->PendingOpList);

    pPendingOp->pIrpContext = PvfsReferenceIrpContext(pIrpContext);
    pPendingOp->pfnCompletion = pfnCompletion;
    pPendingOp->pfnFreeContext = pfnFreeContext;
    pPendingOp->pCompletionContext = pCompletionContext;

    ntError = PvfsListAddTail(
                  pScb->pOplockPendingOpsQueue,
                  (PVOID)pPendingOp);
    BAIL_ON_NT_STATUS(ntError);

    pIrpContext->QueueType = PVFS_QUEUE_TYPE_PENDING_OPLOCK_BREAK;

    pIrpContext->pScb = PvfsReferenceSCB(pScb);

    PvfsIrpMarkPending(
        pIrpContext,
        PvfsQueueCancelIrp,
        pIrpContext);

    /* Set the request in a cancellable state */

    PvfsIrpContextClearFlag(pIrpContext, PVFS_IRP_CTX_FLAG_ACTIVE);

    ntError = STATUS_PENDING;

cleanup:
    LWIO_UNLOCK_MUTEX(bLocked, &pScb->BaseControlBlock.Mutex);

    return ntError;

error:
    LWIO_UNLOCK_MUTEX(bLocked, &pScb->BaseControlBlock.Mutex);

    if (pPendingOp)
    {
        PvfsFreePendingOp(&pPendingOp);
    }
    else
    {
        pfnFreeContext(pCompletionContext);
    }

    goto cleanup;
}

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

BOOLEAN
PvfsStreamIsOplocked(
    IN PPVFS_SCB pScb
    )
{
    return !PvfsListIsEmpty(pScb->pOplockList);
}

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

inline
BOOLEAN
PvfsStreamIsOplockedExclusive(
    IN PPVFS_SCB pScb
    )
{
    return ((pScb->CountOplockBatch > 0) || (pScb->CountOplockLevel1 > 0));
}

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

inline
BOOLEAN
PvfsStreamIsOplockedShared(
    IN PPVFS_SCB pScb
    )
{
    return (pScb->CountOplockLevel2 > 0);
}

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

inline
BOOLEAN
PvfsStreamIsOplockedLeased(
    IN PPVFS_SCB pScb
    )
{
    return ((pScb->CountLeaseR > 0) ||
            (pScb->CountLeaseRH > 0) ||
            (pScb->CountLeaseRW > 0) ||
            (pScb->CountLeaseRWH > 0));
}

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

inline
BOOLEAN
PvfsStreamIsLeaseRead(
    IN PPVFS_SCB pScb
    )
{
    return (pScb->CountLeaseR > 0);
}

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

inline
BOOLEAN
PvfsStreamIsLeaseReadHandle(
    IN PPVFS_SCB pScb
    )
{
    return (pScb->CountLeaseRH > 0);
}

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

inline
BOOLEAN
PvfsStreamIsLeaseReadWrite(
    IN PPVFS_SCB pScb
    )
{
    return (pScb->CountLeaseRW > 0);
}

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

inline
BOOLEAN
PvfsStreamIsLeaseReadWriteHandle(
    IN PPVFS_SCB pScb
    )
{
    return (pScb->CountLeaseRWH > 0);
}

/*****************************************************************************
 ****************************************************************************/

NTSTATUS
PvfsAddOplockRecord(
    IN OUT PPVFS_SCB pScb,
    IN     PPVFS_IRP_CONTEXT pIrpContext,
    IN     PPVFS_CCB pCcb,
    IN     IO_OPLOCK_TYPE OplockType
    )
{
	return PvfsAddOplockOrLeaseRecord(
		       pScb,
		       pIrpContext,
		       pCcb,
		       OplockType,
		       IO_LEASE_STATE_NONE);
}

/*****************************************************************************
 ****************************************************************************/

NTSTATUS
PvfsAddLeaseRecord(
    IN OUT PPVFS_SCB pScb,
    IN     PPVFS_IRP_CONTEXT pIrpContext,
    IN     PPVFS_CCB pCcb,
    IN     IO_LEASE_STATE LeaseState
    )
{
	return PvfsAddOplockOrLeaseRecord(
		       pScb,
		       pIrpContext,
		       pCcb,
		       IO_OPLOCK_REQUEST_LEASE,
		       LeaseState);
}

/*****************************************************************************
 ****************************************************************************/
static
NTSTATUS
PvfsAddOplockOrLeaseRecord(
    IN OUT PPVFS_SCB pScb,
    IN     PPVFS_IRP_CONTEXT pIrpContext,
    IN     PPVFS_CCB pCcb,
    IN     IO_OPLOCK_TYPE OplockType,
    IN     IO_LEASE_STATE LeaseState
    )
{
    NTSTATUS ntError = STATUS_SUCCESS;
    PPVFS_OPLOCK_RECORD pOplock = NULL;

    // Do not add empty no lease state record
    if (IO_OPLOCK_REQUEST_LEASE == OplockType &&
        IO_LEASE_STATE_NONE == LeaseState)
    {
        ntError = STATUS_INVALID_PARAMETER;
        BAIL_ON_NT_STATUS(ntError);
    }

    ntError = PvfsAllocateMemory(
                  OUT_PPVOID(&pOplock),
                  sizeof(PVFS_OPLOCK_RECORD),
                  FALSE);
    BAIL_ON_NT_STATUS(ntError);

    PVFS_INIT_LINKS(&pOplock->OplockList);

    pOplock->OplockType = OplockType;
    if (IO_OPLOCK_REQUEST_LEASE == pOplock->OplockType)
    {
        pOplock->LeaseState = LeaseState;

        LWIO_ASSERT(pCcb->LeaseKey);
        PvfsLeaseKeySetLeaseState(pCcb->LeaseKey, LeaseState);
    }

    pOplock->pCcb = PvfsReferenceCCB(pCcb);
    pOplock->pIrpContext = PvfsReferenceIrpContext(pIrpContext);

    PvfspOplockIncrementCounter(pOplock);

    ntError = PvfsListAddTail(pScb->pOplockList, &pOplock->OplockList);
    BAIL_ON_NT_STATUS(ntError);


error:

    if (!NT_SUCCESS(ntError))
    {
        if (pOplock)
        {
            PvfsFreeOplockRecord(&pOplock);
        }
    }

    return ntError;
}

/*****************************************************************************
 ****************************************************************************/

VOID
PvfsFreeOplockRecord(
    PPVFS_OPLOCK_RECORD *ppOplockRec
    )
{
    PPVFS_OPLOCK_RECORD pOplock = NULL;

    if (ppOplockRec && *ppOplockRec)
    {
        pOplock = *ppOplockRec;

        PvfspOplockDecrementCounter(pOplock);

        if (pOplock->pIrpContext)
        {
            PvfsReleaseIrpContext(&pOplock->pIrpContext);
        }

        if (pOplock->pCcb)
        {
            PvfsReleaseCCB(pOplock->pCcb);
        }

        PVFS_FREE(ppOplockRec);
    }

    return;
}

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

static
VOID
PvfsOplockCleanPendingOpQueue(
    PVOID pContext
    );

NTSTATUS
PvfsScheduleCancelPendingOp(
    PPVFS_IRP_CONTEXT pIrpContext
    )
{
    NTSTATUS ntError = STATUS_UNSUCCESSFUL;
    PPVFS_IRP_CONTEXT pIrpCtx = NULL;

    BAIL_ON_INVALID_PTR(pIrpContext->pScb, ntError);

    pIrpCtx = PvfsReferenceIrpContext(pIrpContext);

    ntError = LwRtlQueueWorkItem(
                  gPvfsDriverState.ThreadPool,
                  PvfsOplockCleanPendingOpQueue,
                  pIrpCtx,
                  LW_SCHEDULE_HIGH_PRIORITY);
    BAIL_ON_NT_STATUS(ntError);

error:
    if (!NT_SUCCESS(ntError))
    {
        if (pIrpCtx)
        {
            PvfsReleaseIrpContext(&pIrpCtx);
        }
    }

    return ntError;
}

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

static
VOID
PvfsOplockCleanPendingOpQueue(
    PVOID pContext
    )
{
    PPVFS_IRP_CONTEXT pIrpCtx = (PPVFS_IRP_CONTEXT)pContext;
    PPVFS_OPLOCK_PENDING_OPERATION pOperation = NULL;
    PLW_LIST_LINKS pOpLink = NULL;
    BOOLEAN bScbLocked = FALSE;

    LWIO_LOCK_MUTEX(bScbLocked, &pIrpCtx->pScb->BaseControlBlock.Mutex);

    for (pOpLink = PvfsListTraverse(pIrpCtx->pScb->pOplockPendingOpsQueue, NULL);
         pOpLink;
         pOpLink = PvfsListTraverse(pIrpCtx->pScb->pOplockPendingOpsQueue, pOpLink))
    {
        pOperation = LW_STRUCT_FROM_FIELD(
                         pOpLink,
                         PVFS_OPLOCK_PENDING_OPERATION,
                         PendingOpList);

        if (pOperation->pIrpContext != pIrpCtx)
        {
            continue;
        }

        PvfsListRemoveItem(pIrpCtx->pScb->pOplockPendingOpsQueue, pOpLink);
        pOpLink = NULL;

        LWIO_UNLOCK_MUTEX(bScbLocked, &pIrpCtx->pScb->BaseControlBlock.Mutex);

        pOperation->pIrpContext->pIrp->IoStatusBlock.Status = STATUS_CANCELLED;

        PvfsCompleteIrpContext(pOperation->pIrpContext);

        PvfsFreePendingOp(&pOperation);

        /* Can only be one IrpContext match so we are done */
    }

    LWIO_UNLOCK_MUTEX(bScbLocked, &pIrpCtx->pScb->BaseControlBlock.Mutex);

    // If we didn't find the IrpContext, it must be in the WorkQueue
    // It will be cleaned up during normal IrpProcessing

    if (pIrpCtx)
    {
        PvfsReleaseIrpContext(&pIrpCtx);
    }

    return;
}

/*****************************************************************************
 ****************************************************************************/

NTSTATUS
PvfsRenameSCB(
    IN PPVFS_SCB pScb,
    IN PPVFS_CCB pCcb,
    IN PPVFS_FILE_NAME pNewStreamName
    )
{
    NTSTATUS ntError = STATUS_SUCCESS;
    PPVFS_SCB pTargetScb = NULL;
    BOOLEAN targetScbListLocked = FALSE;
    BOOLEAN scbRwLocked = FALSE;
    PVFS_FILE_NAME currentFileName = { 0 };
    PCSTR newStreamName = NULL;
    PVFS_STAT Stat = {0};
    PPVFS_FCB pFcb = NULL;
    BOOLEAN fcbLocked = FALSE;

    ntError = PvfsValidatePathSCB(pCcb->pScb, &pCcb->FileId);
    BAIL_ON_NT_STATUS(ntError);

    BAIL_ON_INVALID_PTR(pScb, ntError);

    // if target unnamed stream is non-zero length fail rename
    if (PvfsIsDefaultStreamName(pNewStreamName))
    {
        ntError = PvfsSysStatByFileName(pNewStreamName, &Stat);
        if (STATUS_SUCCESS == ntError && Stat.s_size != 0)
        {
            ntError = STATUS_INVALID_PARAMETER;
            BAIL_ON_NT_STATUS(ntError);
        }
        ntError = STATUS_SUCCESS;
    }

    ntError = PvfsBuildFileNameFromScb(&currentFileName, pScb);
    BAIL_ON_NT_STATUS(ntError);

    pFcb = PvfsReferenceFCB(pScb->pOwnerFcb);

    LWIO_LOCK_RWMUTEX_EXCLUSIVE(fcbLocked, &pFcb->rwScbLock);

    newStreamName = PvfsGetCStringBaseStreamName(pNewStreamName);

    ntError = PvfsLookupSCB_inlock(&pTargetScb, pFcb, pNewStreamName, FALSE);
    switch (ntError)
    {
        case STATUS_SUCCESS:
        {
            LWIO_ASSERT(pTargetScb->pOwnerFcb == pFcb);

            if (pTargetScb != pScb)
            {
                // Remove an existing SCB for the target file/stream (if one exists)
                // But make sure it is a different SCB

                LWIO_LOCK_RWMUTEX_SHARED(targetScbListLocked, &pTargetScb->rwCcbLock);

                if (pTargetScb->OpenHandleCount > 0 )
                {
                    // if TargetScb has open handles cannot rename
                    // This except in the batch-oplock case
                    ntError = STATUS_INVALID_PARAMETER;
                    BAIL_ON_NT_STATUS(ntError);
                }

                LWIO_UNLOCK_RWMUTEX(targetScbListLocked, &pTargetScb->rwCcbLock);

                ntError = PvfsRemoveSCBFromFCB_inlock(pFcb, pTargetScb);
                LWIO_ASSERT(ntError == STATUS_SUCCESS);
            }
        }
        break;

        case STATUS_NOT_FOUND:
            // no existing stream by that name in the open table
            break;

        default:
            BAIL_ON_NT_STATUS(ntError);
    }


    ntError = PvfsSysRenameByFileName(&currentFileName, pNewStreamName);
    // Ignore the error here and continue

    ntError = PvfsPathCacheRemove(&currentFileName);
    // Ignore the error here and continue

    // Remove the SCB from the table, update the lookup key, and then re-add.
    // Otherwise you will get memory corruption as a freed pointer gets left
    // in the Table because if cannot be located using the current (updated)
    // filename.

    // Remove SCB
    ntError = PvfsRemoveSCBFromFCB_inlock(pFcb, pScb);
    LWIO_ASSERT(ntError == STATUS_SUCCESS);

    // Rename SCB
    LWIO_LOCK_RWMUTEX_EXCLUSIVE(scbRwLocked, &pScb->BaseControlBlock.RwLock);
    if (pScb->pszStreamname)
    {
        LwRtlCStringFree(&pScb->pszStreamname);
    }

    ntError = LwRtlCStringDuplicate(
                  &pScb->pszStreamname,
                  newStreamName ? newStreamName : "");
    BAIL_ON_NT_STATUS(ntError);

    LWIO_UNLOCK_RWMUTEX(scbRwLocked, &pScb->BaseControlBlock.RwLock);

    // Re-Add SCB
    ntError = PvfsAddSCBToFCB_inlock(pFcb, pScb);
    BAIL_ON_NT_STATUS(ntError);

error:

    // Release all locks
    LWIO_UNLOCK_RWMUTEX(targetScbListLocked, &pTargetScb->rwCcbLock);
    LWIO_UNLOCK_RWMUTEX(scbRwLocked, &pScb->BaseControlBlock.RwLock);
    LWIO_UNLOCK_RWMUTEX(fcbLocked, &pFcb->rwScbLock);

    if (pFcb)
    {
        PvfsReleaseFCB(&pFcb);
    }

    if (pTargetScb)
    {
        PvfsReleaseSCB(&pTargetScb);
    }

    PvfsDestroyFileName(&currentFileName);

    return ntError;
}

/*****************************************************************************
 ****************************************************************************/

BOOLEAN
PvfsScbIsPendingDelete(
    PPVFS_SCB pScb
    )
{
    BOOLEAN bPendingDelete = FALSE;
    BOOLEAN bIsLocked = FALSE;

    LWIO_LOCK_MUTEX(bIsLocked, &pScb->BaseControlBlock.Mutex);
    bPendingDelete = pScb->bDeleteOnClose || PvfsFcbIsPendingDelete(pScb->pOwnerFcb);
    LWIO_UNLOCK_MUTEX(bIsLocked, &pScb->BaseControlBlock.Mutex);

    return bPendingDelete;
}

/*****************************************************************************
 ****************************************************************************/

VOID
PvfsScbSetPendingDelete(
    PPVFS_SCB pScb,
    BOOLEAN bPendingDelete
    )
{
    BOOLEAN bIsLocked = FALSE;

    LWIO_LOCK_MUTEX(bIsLocked, &pScb->BaseControlBlock.Mutex);
    pScb->bDeleteOnClose = bPendingDelete;
    if (PvfsIsDefaultStream(pScb))
    {
        PvfsFcbSetPendingDelete(pScb->pOwnerFcb, bPendingDelete);
    }
    LWIO_UNLOCK_MUTEX(bIsLocked, &pScb->BaseControlBlock.Mutex);
}


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

BOOLEAN
PvfsIsDefaultStream(
    PPVFS_SCB pScb
    )
{
    BOOLEAN isDefaultStream = FALSE;
    BOOLEAN scbRwLocked = FALSE;

    LWIO_LOCK_RWMUTEX_SHARED(scbRwLocked, &pScb->BaseControlBlock.RwLock);
    if ((pScb->StreamType == PVFS_STREAM_TYPE_DATA) &&
        ((pScb->pszStreamname == NULL) || (*pScb->pszStreamname == '\0')))
    {
        isDefaultStream = TRUE;
    }
    LWIO_UNLOCK_RWMUTEX(scbRwLocked, &pScb->BaseControlBlock.RwLock);

    return isDefaultStream;
}

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

BOOLEAN
PvfsIsDefaultStreamName(
    PPVFS_FILE_NAME pFileName
    )
{
    BOOLEAN isDefaultStream = FALSE;

    if (pFileName->FileName &&
        (pFileName->StreamName == NULL) &&
        (pFileName->Type == PVFS_STREAM_TYPE_DATA))
    {
        isDefaultStream = TRUE;
    }

    return isDefaultStream;
}

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

VOID
PvfsSetScbAllocationSize(
    IN PPVFS_SCB pScb,
    IN LONG64 AllocationSize
    )
{
    BOOLEAN scbLocked = FALSE;

    LWIO_LOCK_MUTEX(scbLocked, &pScb->BaseControlBlock.Mutex);
    pScb->AllocationSize = AllocationSize;
    LWIO_UNLOCK_MUTEX(scbLocked, &pScb->BaseControlBlock.Mutex);

    return;
}

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

LONG64
PvfsGetScbAllocationSize(
    IN PPVFS_SCB pScb
    )
{
    LONG64 allocationSize = 0;
    BOOLEAN scbLocked = FALSE;

    LWIO_LOCK_MUTEX(scbLocked, &pScb->BaseControlBlock.Mutex);
    allocationSize = pScb->AllocationSize;
    LWIO_UNLOCK_MUTEX(scbLocked, &pScb->BaseControlBlock.Mutex);

    return allocationSize;
}

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

static
VOID
PvfspOplockIncrementCounter(
    IN PPVFS_OPLOCK_RECORD OplockRecord
    )
{
    switch (OplockRecord->OplockType)
    {
        case IO_OPLOCK_REQUEST_NONE:
            break;

        case IO_OPLOCK_REQUEST_OPLOCK_BATCH:
            OplockRecord->pCcb->pScb->CountOplockBatch++;
            LWIO_ASSERT(OplockRecord->pCcb->pScb->CountOplockBatch > 0);
            break;

        case IO_OPLOCK_REQUEST_OPLOCK_LEVEL_1:
            OplockRecord->pCcb->pScb->CountOplockLevel1++;
            LWIO_ASSERT(OplockRecord->pCcb->pScb->CountOplockLevel1 > 0);
            break;

        case IO_OPLOCK_REQUEST_OPLOCK_LEVEL_2:
            OplockRecord->pCcb->pScb->CountOplockLevel2++;
            LWIO_ASSERT(OplockRecord->pCcb->pScb->CountOplockLevel2 > 0);
            break;

        case IO_OPLOCK_REQUEST_LEASE:
            switch (OplockRecord->LeaseState)
            {
                case IO_LEASE_STATE_READ:
                    OplockRecord->pCcb->pScb->CountLeaseR++;
                    LWIO_ASSERT(OplockRecord->pCcb->pScb->CountLeaseR > 0);
                    break;

                case (IO_LEASE_STATE_READ|IO_LEASE_STATE_HANDLE):
                    OplockRecord->pCcb->pScb->CountLeaseRH++;
                    LWIO_ASSERT(OplockRecord->pCcb->pScb->CountLeaseRH > 0);
                    break;
                case (IO_LEASE_STATE_READ|IO_LEASE_STATE_WRITE):
                    OplockRecord->pCcb->pScb->CountLeaseRW++;
                    LWIO_ASSERT(OplockRecord->pCcb->pScb->CountLeaseRW > 0);
                    break;

                case (IO_LEASE_STATE_READ|IO_LEASE_STATE_WRITE|IO_LEASE_STATE_HANDLE):
                    OplockRecord->pCcb->pScb->CountLeaseRWH++;
                    LWIO_ASSERT(OplockRecord->pCcb->pScb->CountLeaseRWH > 0);
                    break;

                default:
                    LWIO_ASSERT(FALSE);
            }
            break;

        default:
            LWIO_ASSERT(FALSE);
    }

    return;
}

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

static
VOID
PvfspOplockDecrementCounter(
    IN PPVFS_OPLOCK_RECORD OplockRecord
    )
{
    switch (OplockRecord->OplockType)
    {
        case IO_OPLOCK_REQUEST_OPLOCK_BATCH:
            OplockRecord->pCcb->pScb->CountOplockBatch--;
            LWIO_ASSERT(OplockRecord->pCcb->pScb->CountOplockBatch >= 0);
            break;

        case IO_OPLOCK_REQUEST_OPLOCK_LEVEL_1:
            OplockRecord->pCcb->pScb->CountOplockLevel1--;
            LWIO_ASSERT(OplockRecord->pCcb->pScb->CountOplockLevel1 >= 0);
            break;

        case IO_OPLOCK_REQUEST_OPLOCK_LEVEL_2:
            OplockRecord->pCcb->pScb->CountOplockLevel2--;
            LWIO_ASSERT(OplockRecord->pCcb->pScb->CountOplockLevel2 >= 0);
            break;

        case IO_OPLOCK_REQUEST_LEASE:
            switch (OplockRecord->LeaseState)
            {
                case IO_LEASE_STATE_READ:
                    OplockRecord->pCcb->pScb->CountLeaseR--;
                    LWIO_ASSERT(OplockRecord->pCcb->pScb->CountLeaseR >= 0);
                    break;

                case (IO_LEASE_STATE_READ|IO_LEASE_STATE_HANDLE):
                    OplockRecord->pCcb->pScb->CountLeaseRH--;
                    LWIO_ASSERT(OplockRecord->pCcb->pScb->CountLeaseRH >= 0);
                    break;
                case (IO_LEASE_STATE_READ|IO_LEASE_STATE_WRITE):
                    OplockRecord->pCcb->pScb->CountLeaseRW--;
                    LWIO_ASSERT(OplockRecord->pCcb->pScb->CountLeaseRW >= 0);
                    break;

                case (IO_LEASE_STATE_READ|IO_LEASE_STATE_WRITE|IO_LEASE_STATE_HANDLE):
                    OplockRecord->pCcb->pScb->CountLeaseRWH--;
                    LWIO_ASSERT(OplockRecord->pCcb->pScb->CountLeaseRWH >= 0);
                    break;

                default:
                    LWIO_ASSERT(FALSE);
            }
            break;

        default:
            LWIO_ASSERT(FALSE);
    }

    return;
}
