/* -*- 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:
 *
 *        ccb.c
 *
 * Abstract:
 *
 *        Likewise Posix File System Driver (PVFS)
 *
 *        Context Control Block routineus
 *
 * Authors: Gerald Carter <gcarter@likewise.com>
 *          Krishna Ganugapati <krishnag@likewise.com>
 */

#include "pvfs.h"


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

static
VOID
PvfsFreeAffinityGroup(
    PPVFS_AFFINITY_GROUP *ppGroup
    );

NTSTATUS
PvfsAllocateCCB(
    PPVFS_CCB *ppCCB
    )
{
    NTSTATUS ntError = STATUS_UNSUCCESSFUL;
    PPVFS_CCB pCCB = NULL;

    *ppCCB = NULL;

    ntError = PvfsAllocateMemory(
                  OUT_PPVOID(&pCCB),
                  sizeof(PVFS_CCB),
                  FALSE);
    BAIL_ON_NT_STATUS(ntError);

    InterlockedIncrement(&gPvfsDriverState.Counters.Ccb);

    /* Initialize mutexes and refcounts */

    pthread_mutex_init(&pCCB->ControlBlock, NULL);

    PVFS_INIT_LINKS(&pCCB->ScbList);

    pCCB->OplockState = PVFS_OPLOCK_STATE_NONE;

    pCCB->fd = -1;
    PVFS_CLEAR_FILEID(pCCB->FileId);

    pCCB->pScb = NULL;
    pCCB->pwszShareName = NULL;
    pCCB->pDirContext = NULL;
    pCCB->pUserToken = NULL;
    pCCB->pChangeFilter = NULL;
    pCCB->ChangeEvent = 0;
    pCCB->WriteCount = 0;
    pCCB->FileSize = 0;
    pCCB->AccessGranted = 0;
    pCCB->Flags = PVFS_CCB_FLAG_NONE;

    LwRtlZeroMemory(&pCCB->LockTable, sizeof(pCCB->LockTable));

    pCCB->LeaseKey = NULL;

    ntError = PvfsListInit(
                  &pCCB->pAffinityGroups,
                  0,  /* no max size */
                  (PPVFS_LIST_FREE_DATA_FN)PvfsFreeAffinityGroup);
    ntError = PvfsListInit(
                  &pCCB->pIdleAffinityGroups,
                  0,  /* no max size */
                  (PPVFS_LIST_FREE_DATA_FN)PvfsFreeAffinityGroup);
    BAIL_ON_NT_STATUS(ntError);

    ntError = PvfsListInit(
                  &pCCB->pZctContextList,
                  0,  /* no max size */
                  (PPVFS_LIST_FREE_DATA_FN)PvfsFreeZctContext);
    BAIL_ON_NT_STATUS(ntError);

    /* Add initial ref count */

    pCCB->RefCount = 1;

    *ppCCB = pCCB;

    ntError = STATUS_SUCCESS;

cleanup:
    return ntError;

error:
    if (pCCB)
    {
        PvfsFreeCCB(pCCB);
    }


    goto cleanup;
}

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

NTSTATUS
PvfsFreeCCB(
    PPVFS_CCB pCCB
    )
{
    LWIO_ASSERT(pCCB->RefCount == 0);

    if (pCCB->LeaseKey)
    {
        PvfsLeaseKeyRelease(&pCCB->LeaseKey);
    }

    if (pCCB->pScb)
    {
        PvfsRemoveCCBFromSCB(pCCB->pScb, pCCB);
        PvfsReleaseSCB(&pCCB->pScb);
    }

    if (pCCB->pDirContext)
    {
        PvfsFreeDirectoryContext(pCCB->pDirContext);
    }

    if (pCCB->pUserToken)
    {
        RtlReleaseAccessToken(&pCCB->pUserToken);
        pCCB->pUserToken = NULL;
    }

    PvfsListDestroy(&pCCB->pAffinityGroups);
    PvfsListDestroy(&pCCB->pIdleAffinityGroups);
    PvfsListDestroy(&pCCB->pZctContextList);

    LwRtlWC16StringFree(&pCCB->pwszShareName);

    PVFS_FREE(&pCCB->LockTable.ExclusiveLocks.pLocks);
    PVFS_FREE(&pCCB->LockTable.SharedLocks.pLocks);

    pthread_mutex_destroy(&pCCB->ControlBlock);

    PVFS_FREE(&pCCB);

    InterlockedDecrement(&gPvfsDriverState.Counters.Ccb);

    return STATUS_SUCCESS;
}

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

VOID
PvfsReleaseCCB(
    PPVFS_CCB pCCB
    )
{
    if (InterlockedDecrement(&pCCB->RefCount) == 0)
    {
        PvfsFreeCCB(pCCB);
    }

    return;
}

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

PPVFS_CCB
PvfsReferenceCCB(
    PPVFS_CCB pCCB
    )
{
    InterlockedIncrement(&pCCB->RefCount);

    return pCCB;
}


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

static
NTSTATUS
PvfsAcquireCCBInternal(
    IO_FILE_HANDLE FileHandle,
    PPVFS_CCB * ppCCB,
    BOOLEAN bIncRef
    )
{
    NTSTATUS ntError = STATUS_SUCCESS;
    PPVFS_CCB pCCB = (PPVFS_CCB)IoFileGetContext(FileHandle);

    PVFS_BAIL_ON_INVALID_CCB(pCCB, ntError);

    if (bIncRef) {
        InterlockedIncrement(&pCCB->RefCount);
    }

    *ppCCB = pCCB;

cleanup:
    return ntError;

error:
    *ppCCB = NULL;

    goto cleanup;

}

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

NTSTATUS
PvfsAcquireCCB(
    IO_FILE_HANDLE FileHandle,
    PPVFS_CCB * ppCCB
    )
{
    return PvfsAcquireCCBInternal(FileHandle, ppCCB, TRUE);
}

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

NTSTATUS
PvfsAcquireCCBClose(
    IO_FILE_HANDLE FileHandle,
    PPVFS_CCB * ppCCB
    )
{
    return PvfsAcquireCCBInternal(FileHandle, ppCCB, FALSE);
}


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

NTSTATUS
PvfsStoreCCB(
    IO_FILE_HANDLE FileHandle,
    PPVFS_CCB pCCB
    )
{
    NTSTATUS ntError = STATUS_SUCCESS;

    ntError = IoFileSetContext(FileHandle, (PVOID)pCCB);
    BAIL_ON_NT_STATUS(ntError);

cleanup:
    return ntError;

error:
    goto cleanup;
}

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

static
NTSTATUS
PvfsCreateAffinityGroup(
    PPVFS_CCB pCcb,
    PPVFS_AFFINITY_GROUP *ppNewGroup
    );

NTSTATUS
PvfsScheduleIoContext(
    PPVFS_PENDING_IO pIoReq,
    IN  PPVFS_PROCESS_IO_CB pfnProcessIo
    )
{
    NTSTATUS ntError = STATUS_SUCCESS;
    PPVFS_DRIVER_STATE pState = &gPvfsDriverState;
    PPVFS_CCB pCcb = pIoReq->pCcb;
    PPVFS_AFFINITY_GROUP pBestGroup = NULL;
    PPVFS_AFFINITY_GROUP pNewGroup = NULL;
    PPVFS_AFFINITY_GROUP pLowestLoadGroup = NULL;
    PLW_LIST_LINKS pLinks = NULL;
    BOOLEAN bCcbLocked = FALSE;
    ULONG64 ByteOffset = 0;
    LONG LowestLoad = MAXLONG;

    pIoReq->pfnProcessIo = pfnProcessIo;

    /*
     * Group all I/Os at "SEEK_CUR" together at "ByteOffset 0".
     * They are, by definition, completely sequential I/O.
     */
    if (pIoReq->pIrpContext->pIrp->Args.ReadWrite.ByteOffset)
    {
        ByteOffset = *pIoReq->pIrpContext->pIrp->Args.ReadWrite.ByteOffset;
    }

    LWIO_LOCK_MUTEX(bCcbLocked, &pCcb->ControlBlock);
    while ((pLinks = PvfsListTraverse(pCcb->pAffinityGroups, pLinks)) != NULL)
    {
        PPVFS_AFFINITY_GROUP pCurGroup = NULL;
        PLW_LIST_LINKS pReqLinks = NULL;
        PPVFS_PENDING_IO pCurReq = NULL;
        ULONG64 CurByteOffset = 0;

        pCurGroup = LW_STRUCT_FROM_FIELD(
                        pLinks,
                        PVFS_AFFINITY_GROUP,
                        AffinityGroups);

        pReqLinks = PvfsListTraverse(pCurGroup->pReqList, pReqLinks);
        LWIO_ASSERT(pReqLinks != NULL);
        pCurReq = LW_STRUCT_FROM_FIELD(
                        pReqLinks,
                        PVFS_PENDING_IO,
                        AffinityGroupReqs);

        if (pLowestLoadGroup == NULL ||
            PvfsListLength(pLowestLoadGroup->pReqList) < LowestLoad)
        {
            pLowestLoadGroup = pCurGroup;
            LowestLoad = PvfsListLength(pLowestLoadGroup->pReqList);
        }

        CurByteOffset = 0;
        if (pCurReq->pIrpContext->pIrp->Args.ReadWrite.ByteOffset)
        {
            CurByteOffset =
                *pCurReq->pIrpContext->pIrp->Args.ReadWrite.ByteOffset;
        }

        if ((ByteOffset >= CurByteOffset && 
             (ByteOffset - CurByteOffset) < pState->Config.AffinityByteRange) ||
            (CurByteOffset > ByteOffset && 
             (CurByteOffset - ByteOffset) < pState->Config.AffinityByteRange))
        {
            pBestGroup = pCurGroup;
            break;
        }
    }
    if (pBestGroup == NULL)
    {
        if (!PvfsListIsEmpty(pCcb->pIdleAffinityGroups))
        {
            PvfsListRemoveHead(pCcb->pIdleAffinityGroups, &pLinks);
            PvfsListAddTail(pCcb->pAffinityGroups, pLinks);
            pBestGroup = LW_STRUCT_FROM_FIELD(
                            pLinks,
                            PVFS_AFFINITY_GROUP,
                            AffinityGroups);
        }
        else if (PvfsListLength(pCcb->pAffinityGroups) >
                 pState->Config.AffinityGroupsPerFile)
        {
            pBestGroup = pLowestLoadGroup;
        }
        else
        {
            ntError = PvfsCreateAffinityGroup(pCcb, &pNewGroup);
            BAIL_ON_NT_STATUS(ntError);

            ntError = PvfsListAddTail(
                            pCcb->pAffinityGroups,
                            &pNewGroup->AffinityGroups);
            BAIL_ON_NT_STATUS(ntError);

            pBestGroup = pNewGroup;
            pNewGroup = NULL;
        }
    }

cleanup:

    LWIO_ASSERT(pBestGroup != NULL);

    pIoReq->pIrpContext->QueueType = PVFS_QUEUE_TYPE_IO;

    PvfsIrpMarkPending(
        pIoReq->pIrpContext,
        PvfsQueueCancelIrp,
        pIoReq->pIrpContext);

    /* Set the request in a cancellable state */
    PvfsIrpContextClearFlag(pIoReq->pIrpContext, PVFS_IRP_CTX_FLAG_ACTIVE);

    PvfsListAddTail(pBestGroup->pReqList, &pIoReq->AffinityGroupReqs);

    if (pBestGroup->bScheduled == FALSE)
    {
        pBestGroup->bScheduled = TRUE;
        LwRtlScheduleWorkItem(pBestGroup->pWorkItem, 0);
    }

    LWIO_UNLOCK_MUTEX(bCcbLocked, &pCcb->ControlBlock);

    return STATUS_PENDING;

error:

    PvfsFreeAffinityGroup(&pNewGroup);
    pBestGroup = pLowestLoadGroup;
    goto cleanup;
}

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

NTSTATUS
PvfsCreateIoContext(
    OUT PPVFS_PENDING_IO *ppIoContext,
    IN  PPVFS_IRP_CONTEXT pIrpContext,
    IN  PPVFS_CCB pCcb
    )
{
    NTSTATUS ntError = STATUS_UNSUCCESSFUL;
    PPVFS_PENDING_IO pIoCtx = NULL;

    ntError = PvfsAllocateMemory(
                  OUT_PPVOID(&pIoCtx),
                  sizeof(PVFS_PENDING_IO),
                  FALSE);
    BAIL_ON_NT_STATUS(ntError);

    pIoCtx->pZctContext = NULL;
    pIoCtx->pIrpContext = PvfsReferenceIrpContext(pIrpContext);
    pIoCtx->pfnProcessIo = NULL;
    pIoCtx->pCcb = PvfsReferenceCCB(pCcb);

    *ppIoContext = pIoCtx;

    ntError = STATUS_SUCCESS;

cleanup:
    return ntError;

error:
    PvfsFreeIoContext(OUT_PPVOID(&pIoCtx));

    goto cleanup;
}


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

VOID
PvfsFreeIoContext(
    IN OUT PVOID *ppContext
    )
{
    PPVFS_PENDING_IO pIoCtx = NULL;

    if (ppContext && *ppContext)
    {
        pIoCtx = *ppContext;

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

        if (pIoCtx->pZctContext)
        {
            PvfsFreeZctContext(&pIoCtx->pZctContext);
        }

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

        PVFS_FREE(ppContext);
    }

    return;
}


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

static
VOID
PvfsProcessIoContext(
    PPVFS_PENDING_IO pIoCtx
    )
{
    NTSTATUS ntError = STATUS_SUCCESS;
    PPVFS_IRP_CONTEXT pIrpCtx = pIoCtx->pIrpContext;
    BOOLEAN bActive = FALSE;

    PvfsQueueCancelIrpIfRequested(pIrpCtx);

    bActive = PvfsIrpContextMarkIfNotSetFlag(
                  pIrpCtx,
                  PVFS_IRP_CTX_FLAG_CANCELLED,
                  PVFS_IRP_CTX_FLAG_ACTIVE);

    if (!bActive)
    {
        ntError = STATUS_CANCELLED;
        BAIL_ON_NT_STATUS(ntError);
    }

    ntError = pIoCtx->pfnProcessIo(pIoCtx);

error:
    LWIO_ASSERT(ntError != STATUS_PENDING);
    pIrpCtx->pIrp->IoStatusBlock.Status = ntError;
    PvfsAsyncCompleteIrpContext(pIrpCtx);
    PvfsFreeIoContext(OUT_PPVOID(&pIoCtx));
}


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

static
VOID
PvfsAffinityGroupWorkItem(
    PLW_WORK_ITEM pWorkItem,
    PVOID pContext
    )
{
    PPVFS_DRIVER_STATE pState = &gPvfsDriverState;
    PPVFS_AFFINITY_GROUP pGroup = pContext;
    PPVFS_CCB pCcb = pGroup->pCcb;
    LONG lReqsProcessed = 0;
    BOOLEAN bCcbLocked = FALSE;
    BOOLEAN bReqsToProcess = TRUE;
    PLW_LIST_LINKS pLinks;

    PvfsReferenceCCB(pCcb);

    // Yield thread to other work every so often.
    while (bReqsToProcess &&
           lReqsProcessed < pState->Config.AffinityWorkItemYieldInterval)
    {
        NTSTATUS ntStatus = STATUS_SUCCESS;
        PPVFS_PENDING_IO pIoReq = NULL;

        LWIO_LOCK_MUTEX(bCcbLocked, &pCcb->ControlBlock);

        ntStatus = PvfsListRemoveHead(pGroup->pReqList, &pLinks);
        LWIO_ASSERT(ntStatus == STATUS_SUCCESS);

        if (PvfsListIsEmpty(pGroup->pReqList))
        {
            PvfsListRemoveItem(pCcb->pAffinityGroups, &pGroup->AffinityGroups);
            PvfsListAddTail(pCcb->pIdleAffinityGroups, &pGroup->AffinityGroups);
            pGroup->bScheduled = FALSE;
            bReqsToProcess = FALSE;
        }

        LWIO_UNLOCK_MUTEX(bCcbLocked, &pCcb->ControlBlock);

        pIoReq = LW_STRUCT_FROM_FIELD(
                        pLinks,
                        PVFS_PENDING_IO,
                        AffinityGroupReqs);
        PvfsProcessIoContext(pIoReq);
        lReqsProcessed++;
    }
    if (bReqsToProcess)
    {
        LwRtlScheduleWorkItem(pGroup->pWorkItem, 0);
    }

    PvfsReleaseCCB(pCcb);
}


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

static
NTSTATUS
PvfsCreateAffinityGroup(
    PPVFS_CCB pCcb,
    PPVFS_AFFINITY_GROUP *ppNewGroup
    )
{
    NTSTATUS ntError = STATUS_SUCCESS;
    PPVFS_DRIVER_STATE pState = &gPvfsDriverState;
    PPVFS_AFFINITY_GROUP pNewGroup = NULL;

    ntError = PvfsAllocateMemory(
                  OUT_PPVOID(&pNewGroup),
                  sizeof(*pNewGroup),
                  FALSE);
    BAIL_ON_NT_STATUS(ntError);

    pNewGroup->bScheduled = FALSE;
    pNewGroup->pCcb = pCcb;
    PVFS_INIT_LINKS(&pNewGroup->AffinityGroups);

    ntError = LwRtlCreateWorkItem(
                    pState->ThreadPool,
                    &pNewGroup->pWorkItem,
                    PvfsAffinityGroupWorkItem,
                    pNewGroup);
    BAIL_ON_NT_STATUS(ntError);

    ntError = PvfsListInit(&pNewGroup->pReqList, 0, NULL);
    BAIL_ON_NT_STATUS(ntError);

    InterlockedIncrement(&pState->Counters.AffinityGroup);

cleanup:

    *ppNewGroup = pNewGroup;

    return ntError;

error:

    PvfsFreeAffinityGroup(&pNewGroup);

    goto cleanup;
}


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

static
VOID
PvfsFreeAffinityGroup(
    PPVFS_AFFINITY_GROUP *ppGroup
    )
{
    PPVFS_AFFINITY_GROUP pGroup = *ppGroup;

    if (pGroup)
    {
        LwRtlFreeWorkItem(&pGroup->pWorkItem);
        PvfsListDestroy(&pGroup->pReqList);
        PVFS_FREE(ppGroup);
        InterlockedDecrement(&gPvfsDriverState.Counters.AffinityGroup);
    }
}


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

NTSTATUS
PvfsSaveFileDeviceInfo(
    PPVFS_CCB pCcb
    )
{
    NTSTATUS ntError = STATUS_UNSUCCESSFUL;
    PVFS_STAT Stat = {0};
    PPVFS_SCB pScb = pCcb->pScb;
    PPVFS_FCB pFcb = pCcb->pScb->pOwnerFcb;
    BOOLEAN scbLocked = FALSE;
    BOOLEAN fcbLocked = FALSE;

    ntError = PvfsSysFstat(pCcb->fd, &Stat);
    BAIL_ON_NT_STATUS(ntError);

    pCcb->FileId.Device = Stat.s_dev;
    pCcb->FileId.Inode  = Stat.s_ino;
    pCcb->FileSize = Stat.s_size;

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

    // Have to set the FileId on the Stream Block
    pScb->FileId = pCcb->FileId;

    // Push the default stream FileId to the File Block.
    // If pScb is not the default stream and the FileId is not valid then
    // get FileId data for the default stream and push it to the File Block.
    // This latter scenario arises when the alternative stream is created/opened
    // before the default stream is created/opened.
    
    if (PvfsIsDefaultStream(pScb) || (!PVFS_VALID_FILEID(pFcb->FileId)))
    {

        if (PvfsIsDefaultStream(pScb))
        {
            LWIO_LOCK_MUTEX(fcbLocked, &pFcb->BaseControlBlock.Mutex);
            pFcb->FileId = pScb->FileId;
        }
        else 
        {   
            // not default stream and not set
            ntError = PvfsSysStat(pFcb->pszFilename, &Stat);
            BAIL_ON_NT_STATUS(ntError);

            LWIO_LOCK_MUTEX(fcbLocked, &pFcb->BaseControlBlock.Mutex);
            pFcb->FileId.Device = Stat.s_dev;
            pFcb->FileId.Inode = Stat.s_ino;
        }
    }

error:
    LWIO_UNLOCK_MUTEX(fcbLocked, &pFcb->BaseControlBlock.Mutex);
    LWIO_UNLOCK_MUTEX(scbLocked, &pScb->BaseControlBlock.Mutex);

    return ntError;
}


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

static
NTSTATUS
PvfsRenameFile(
    PPVFS_CCB pCcb,
    PPVFS_FILE_NAME pNewFileName
    );

static
NTSTATUS
PvfsRenameStream(
    PPVFS_CCB pCcb,
    PPVFS_FILE_NAME pNewStreamName
    );

NTSTATUS
PvfsRenameCCB(
    IN PPVFS_CCB pCcb,
    IN PPVFS_FILE_NAME pDestFileName
    )
{
    NTSTATUS ntError = STATUS_SUCCESS;
    PVFS_FILE_NAME srcFileName = { 0 };

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

    ntError = PvfsBuildFileNameFromScb(&srcFileName, pCcb->pScb);
    BAIL_ON_NT_STATUS(ntError);

    if (!PvfsIsDefaultStreamName(&srcFileName) &&
        !PvfsIsDefaultStreamName(pDestFileName))
    {
        // Two named streams
        if (LwRtlCStringIsEqual(
                PvfsGetCStringBaseStreamName(&srcFileName),
                PvfsGetCStringBaseStreamName(pDestFileName),
                FALSE))
        {
            // Both src and dst stream names are the same
            // renaming the underlying file object

            ntError = PvfsRenameFile(pCcb, pDestFileName);
        }
        else if (LwRtlCStringIsEqual(
                     PvfsGetCStringBaseFileName(&srcFileName),
                     PvfsGetCStringBaseFileName(pDestFileName),
                     FALSE))
        {
            // Renaming the named stream, file name stays the same

            ntError = PvfsRenameStream(pCcb, pDestFileName);
        }
        else
        {
            // Don't allow renaming both the file name and stream name at the
            // same time (yet)

            ntError = STATUS_OBJECT_NAME_INVALID;
        }
    }
    else if (PvfsIsDefaultStreamName(&srcFileName) &&
             PvfsIsDefaultStreamName(pDestFileName))
    {
        // Two default streams rename object itself
        ntError = PvfsRenameFile(pCcb, pDestFileName);
    }
    else if (!PvfsIsDefaultStreamName(&srcFileName) &&
             PvfsIsDefaultStreamName(pDestFileName))
    {
        // rename name stream -> default stream
        // A stream on a directory cannot be renamed to the default data stream
        if (!LwRtlCStringIsEqual(
                         PvfsGetCStringBaseFileName(&srcFileName),
                         PvfsGetCStringBaseFileName(pDestFileName),
                         FALSE) || PVFS_IS_DIR(pCcb))
        {
            ntError = STATUS_OBJECT_NAME_INVALID;
        }
        else
        {
            ntError = PvfsRenameStream(pCcb, pDestFileName);
        }
    }
    else
    {
        // disallow rename object->stream as what smbtorture expects
        // TODO:
        // we may want to allow "Renaming" the default data stream
        // it is not a true rename, and it leaves behind a zero-length default data streams
        ntError = STATUS_OBJECT_NAME_INVALID;
    }
    BAIL_ON_NT_STATUS(ntError);

error:
    PvfsDestroyFileName(&srcFileName);

    return ntError;
}

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

static
NTSTATUS
PvfsRenameFile(
    PPVFS_CCB pCcb,
    PPVFS_FILE_NAME pNewFileName
    )
{
    return PvfsRenameFCB(pCcb->pScb->pOwnerFcb, pCcb, pNewFileName);
}


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

static
NTSTATUS
PvfsRenameStream(
    PPVFS_CCB pCcb,
    PPVFS_FILE_NAME pNewStreamName
    )
{
    return PvfsRenameSCB(pCcb->pScb, pCcb, pNewStreamName);
}
