/* -*- 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:
 *
 *        srvfile2.c
 *
 * Abstract:
 *
 *        Likewise IO (LWIO) - SRV
 *
 *        Elements
 *
 *        File Object (Version 2)
 *
 * Authors: Sriram Nambakam (snambakam@likewise.com)
 */

#include "includes.h"

static
BOOLEAN
SrvFile2IsInParent_inlock(
    PLWIO_SRV_FILE_2 pFile
    );

static
VOID
SrvFile2Free(
    PLWIO_SRV_FILE_2 pFile
    );

static
BOOLEAN
SrvFile2IsRundown_inlock(
    PLWIO_SRV_FILE_2 pFile
    );

static
VOID
SrvFile2SetRundown_inlock(
    PLWIO_SRV_FILE_2 pFile
    );

NTSTATUS
SrvFile2Create(
    PLWIO_SRV_TREE_2        pTree,
    PSMB2_FID               pFid,
    PWSTR                   pwszFilename,
    PIO_FILE_HANDLE         phFile,
    PIO_FILE_NAME*          ppFilename,
    ACCESS_MASK             desiredAccess,
    LONG64                  allocationSize,
    FILE_ATTRIBUTES         fileAttributes,
    FILE_SHARE_FLAGS        shareAccess,
    FILE_CREATE_DISPOSITION createDisposition,
    FILE_CREATE_OPTIONS     createOptions,
    LONG64                  SnapshotTime,
    PLWIO_SRV_FILE_2*       ppFile
    )
{
    NTSTATUS ntStatus = 0;
    PLWIO_SRV_FILE_2 pFile = NULL;

    LWIO_LOG_DEBUG("Creating file [fid: (persistent:%08X)(volatile:%08X)]",
                   pFid->ullPersistentId,
                   pFid->ullVolatileId);

    ntStatus = SrvAllocateMemory(
                    sizeof(LWIO_SRV_FILE_2),
                    (PVOID*)&pFile);
    BAIL_ON_NT_STATUS(ntStatus);

    pFile->refcount = 1;

    pthread_rwlock_init(&pFile->mutex, NULL);
    pFile->pMutex = &pFile->mutex;

    pFile->pTree = pTree;
    SrvTree2Acquire(pTree);

    ntStatus = SrvAllocateStringW(pwszFilename, &pFile->pwszFilename);
    BAIL_ON_NT_STATUS(ntStatus);

    // Allocate async close file state before setting hFile field.
    ntStatus = SrvElementsCreateAsyncCloseFileState(
                    &pFile->pAsyncCloseFileState);
    BAIL_ON_NT_STATUS(ntStatus);

    pFile->fid = *pFid;
    pFile->hFile = *phFile;
    *phFile = NULL;
    pFile->pFilename = *ppFilename;
    *ppFilename = NULL;
    pFile->OpenState.desiredAccess = desiredAccess;
    pFile->OpenState.allocationSize = allocationSize;
    pFile->OpenState.fileAttributes = fileAttributes;
    pFile->OpenState.shareAccess = shareAccess;
    pFile->OpenState.createDisposition = createDisposition;
    pFile->OpenState.createOptions = createOptions;
    pFile->OpenState.SnapshotTime = SnapshotTime;

    pFile->resource.resourceType                 = SRV_RESOURCE_TYPE_FILE;
    pFile->resource.pAttributes                  = &pFile->resourceAttrs;
    pFile->resource.pAttributes->protocolVersion = SMB_PROTOCOL_VERSION_2;
    pFile->resource.pAttributes->fileId.pFid2    = &pFile->fid;
    pFile->resource.pAttributes->treeId.usTid     = pTree->ulTid;
    pFile->resource.pAttributes->sessionId.ullUid = pTree->ullUid;
    pFile->resource.pAttributes->ulConnectionResourceId =
                                  pTree->resourceAttrs.ulConnectionResourceId;

    LWIO_LOG_DEBUG( "Associating file [object:0x%x]"
                    "[fid: (persistent:%08X)(volatile:%08X)]",
                    pFile,
                    pFid->ullPersistentId,
                    pFid->ullVolatileId);

    SRV_ELEMENTS_INCREMENT_OPEN_FILES;

    *ppFile = pFile;

cleanup:

    return ntStatus;

error:

    *ppFile = NULL;

    if (pFile)
    {
        SrvFile2Release(pFile);
    }

    goto cleanup;
}

BOOLEAN
SrvFile2IsInParent(
    PLWIO_SRV_FILE_2 pFile
    )
{
    BOOLEAN bIsInParent = FALSE;
    BOOLEAN bInLock = FALSE;

    LWIO_LOCK_RWMUTEX_EXCLUSIVE(bInLock, &pFile->mutex);
    bIsInParent = SrvFile2IsInParent_inlock(pFile);
    LWIO_UNLOCK_RWMUTEX(bInLock, &pFile->mutex);

    return bIsInParent;
}

static
BOOLEAN
SrvFile2IsInParent_inlock(
    PLWIO_SRV_FILE_2 pFile
    )
{
    return IsSetFlag(pFile->objectFlags, SRV_OBJECT_FLAG_IN_PARENT);
}

VOID
SrvFile2SetInParent(
    PLWIO_SRV_FILE_2 pFile
    )
{
    BOOLEAN bInLock = FALSE;

    LWIO_LOCK_RWMUTEX_EXCLUSIVE(bInLock, &pFile->mutex);
    LWIO_ASSERT(!IsSetFlag(pFile->objectFlags, SRV_OBJECT_FLAG_IN_PARENT));
    SetFlag(pFile->objectFlags, SRV_OBJECT_FLAG_IN_PARENT);
    LWIO_UNLOCK_RWMUTEX(bInLock, &pFile->mutex);
}

VOID
SrvFile2ClearInParent(
    PLWIO_SRV_FILE_2 pFile
    )
{
    BOOLEAN bInLock = FALSE;

    LWIO_LOCK_RWMUTEX_EXCLUSIVE(bInLock, &pFile->mutex);
    LWIO_ASSERT(IsSetFlag(pFile->objectFlags, SRV_OBJECT_FLAG_IN_PARENT));
    ClearFlag(pFile->objectFlags, SRV_OBJECT_FLAG_IN_PARENT);
    LWIO_UNLOCK_RWMUTEX(bInLock, &pFile->mutex);
}

NTSTATUS
SrvFile2SetOplock(
    PLWIO_SRV_FILE_2 pFile,
    HANDLE           hOplock
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    BOOLEAN  bInLock  = FALSE;

    LWIO_LOCK_RWMUTEX_EXCLUSIVE(bInLock, &pFile->mutex);

    if (pFile->hOplock)
    {
        SrvCancelOplock(pFile->hOplock);
        SrvReleaseOplock(pFile->hOplock);
        pFile->hOplock = NULL;
    }

    if (SrvFile2IsRundown_inlock(pFile))
    {
        ntStatus = STATUS_INVALID_HANDLE;
        BAIL_ON_NT_STATUS(ntStatus);
    }

    pFile->hOplock = hOplock;

cleanup:

    LWIO_UNLOCK_RWMUTEX(bInLock, &pFile->mutex);

    return ntStatus;

error:

    goto cleanup;
}

VOID
SrvFile2ResetOplock(
    PLWIO_SRV_FILE_2 pFile
    )
{
    BOOLEAN  bInLock  = FALSE;

    LWIO_LOCK_RWMUTEX_EXCLUSIVE(bInLock, &pFile->mutex);

    if (pFile->hOplock)
    {
        SrvReleaseOplock(pFile->hOplock);
        pFile->hOplock = NULL;
    }

    LWIO_UNLOCK_RWMUTEX(bInLock, &pFile->mutex);
}

VOID
SrvFile2SetClosing(
    PLWIO_SRV_FILE_2 pFile
    )
{
    BOOLEAN bInLock = FALSE;

    LWIO_LOCK_RWMUTEX_EXCLUSIVE(bInLock, &pFile->mutex);

    pFile->bIsClosing = TRUE;

    LWIO_UNLOCK_RWMUTEX(bInLock, &pFile->mutex);
}


BOOLEAN
SrvFile2IsFileClosing(
    PLWIO_SRV_FILE_2 pFile
    )
{
    BOOLEAN bClosing = FALSE;

    BOOLEAN bInLock = FALSE;

    LWIO_LOCK_RWMUTEX_SHARED(bInLock, &pFile->mutex);

    bClosing = pFile->bIsClosing;

    LWIO_UNLOCK_RWMUTEX(bInLock, &pFile->mutex);

    return bClosing;
}

VOID
SrvFile2RegisterLock(
    PLWIO_SRV_FILE_2 pFile
    )
{
    BOOLEAN bInLock = FALSE;

    LWIO_LOCK_RWMUTEX_EXCLUSIVE(bInLock, &pFile->mutex);

    pFile->ulNumLocks++;

    LWIO_UNLOCK_RWMUTEX(bInLock, &pFile->mutex);
}

VOID
SrvFile2RegisterUnlock(
    PLWIO_SRV_FILE_2 pFile
    )
{
    BOOLEAN bInLock = FALSE;

    LWIO_LOCK_RWMUTEX_EXCLUSIVE(bInLock, &pFile->mutex);

    pFile->ulNumLocks--;

    LWIO_UNLOCK_RWMUTEX(bInLock, &pFile->mutex);
}

PLWIO_SRV_FILE_2
SrvFile2Acquire_impl(
    PLWIO_SRV_FILE_2 pFile,
    PCSTR   func,
    LONG    line
    )
{
    LW_LONG ref = InterlockedIncrement(&pFile->refcount);

    LWIO_LOG_DEBUG("Acquiring file [fid: (persistent:%016lX)(volatile:%016lX)"
                    "(ref:%d)] %s()%d",
                    pFile->fid.ullPersistentId,
                    pFile->fid.ullVolatileId,
                    ref,
                    func,
                    line);

    return pFile;
}

VOID
SrvFile2Release_impl(
    PLWIO_SRV_FILE_2 pFile,
    PCSTR   func,
    LONG    line
    )
{
    LW_LONG ref = InterlockedDecrement(&pFile->refcount);

    LWIO_LOG_DEBUG("Releasing file [fid: (persistent:%016lX)(volatile:%016lX)]"
                    "(ref:%d)] %s()%d",
                    pFile->fid.ullPersistentId,
                    pFile->fid.ullVolatileId,
                    ref,
                    func,
                    line);

    if (ref == 0)
    {
        SRV_ELEMENTS_DECREMENT_OPEN_FILES;

        SrvFile2Free(pFile);
    }
}

VOID
SrvFile2BlockIdleTimeout(
    PLWIO_SRV_FILE_2 pFile
    )
{
    if (!pFile->bIsBlockingIdleTimeout)
    {
        SrvConnectionIncrementIdleDisableCount(pFile->pTree->pSession->pConnection);
        pFile->bIsBlockingIdleTimeout = TRUE;
    }
}

VOID
SrvFile2UnblockIdleTimeout(
    PLWIO_SRV_FILE_2 pFile
    )
{
    if (pFile->bIsBlockingIdleTimeout)
    {
        SrvConnectionDecrementIdleDisableCount(pFile->pTree->pSession->pConnection);
        pFile->bIsBlockingIdleTimeout = FALSE;
    }
}

static
VOID
SrvFile2CancelIoForRundown(
    PLWIO_SRV_FILE_2 pFile
    )
{
    if (pFile->hOplock)
    {
        SrvCancelOplock(pFile->hOplock);
    }
    IoCancelForRundownFile(pFile->hFile);
}

VOID
SrvFile2Rundown(
    PLWIO_SRV_FILE_2 pFile
    )
{
    BOOLEAN bInLock = FALSE;
    BOOLEAN bDoRundown = FALSE;
    BOOLEAN bIsInParent = FALSE;
    BOOLEAN bIsClosing = FALSE;

    LWIO_LOCK_RWMUTEX_EXCLUSIVE(bInLock, &pFile->mutex);

    if (!SrvFile2IsRundown_inlock(pFile))
    {
        SrvFile2SetRundown_inlock(pFile);

        bDoRundown = TRUE;
        bIsInParent = SrvFile2IsInParent_inlock(pFile);
        bIsClosing = pFile->bIsClosing;
    }

    LWIO_UNLOCK_RWMUTEX(bInLock, &pFile->mutex);

    if (bIsInParent)
    {
        SrvTree2RemoveFile(pFile->pTree, pFile);
    }

    if (bDoRundown)
    {
        if (pFile->resource.ulResourceId)
        {
            PSRV_RESOURCE pResource = NULL;

            SrvElementsUnregisterResource(pFile->resource.ulResourceId, &pResource);
            pFile->resource.ulResourceId = 0;
        }

        if (pFile->bIsDurable)
        {
            LWIO_LOCK_RWMUTEX_EXCLUSIVE(bInLock, &pFile->mutex);

            if (pFile->pDurable)
            {
                UCHAR oplockLevel = SMB2_OPLOCK_LEVEL_NONE;
                ULONG leaseState = SMB2_LEASE_NONE;

                if (pFile->hOplock)
                {
                    SrvGetOplockLevel(
                        pFile->hOplock,
                        &oplockLevel,
                        &leaseState);
                }

                // Close the file if
                // (a) we were asked to, or
                // (b) the FileHandle doesn't possess a BATCH oplock or
                //     HANDLE caching lease

                if (bIsClosing ||
                    (oplockLevel == SMB2_OPLOCK_LEVEL_NONE) ||
                    (oplockLevel == SMB2_OPLOCK_LEVEL_I) ||
                    (oplockLevel == SMB2_OPLOCK_LEVEL_II) ||
                    ((oplockLevel == SMB2_OPLOCK_LEVEL_LEASE) &&
                     !IsSetFlag(leaseState, SMB2_LEASE_HANDLE_CACHING)))
                {
                    SrvSmb2DurableClose(
                        SrvGetDurableHandleTable(),
                        pFile->pDurable,
                        pFile);

                    SrvFile2CancelIoForRundown(pFile);
                }
                else
                {
                    if (SrvSmb2DurableOrphan(
                            SrvGetDurableHandleTable(),
                            pFile->pDurable,
                            pFile->hFile,
                            &pFile->hOplock,
                            pFile->OpenState.DurableTimeout) == STATUS_SUCCESS)
                    {
                        IoRetainFile(pFile->hFile);
                        pFile->bReleaseOnly = TRUE;
                    }
                    else
                    {
                        SrvSmb2DurableClose(
                            SrvGetDurableHandleTable(),
                            pFile->pDurable,
                            pFile);

                        SrvFile2CancelIoForRundown(pFile);
                    }
                }
                pFile->pDurable = NULL;
            }
            else
            {
                pFile->bReleaseOnly = TRUE;
            }

            LWIO_UNLOCK_RWMUTEX(bInLock, &pFile->mutex);
        }
        else if (pFile->hFile)
        {
            SrvFile2CancelIoForRundown(pFile);
        }

        SrvFile2ResetOplock(pFile);

        SrvFile2UnblockIdleTimeout(pFile);
    }


}

BOOLEAN
SrvFile2IsRundown(
    PLWIO_SRV_FILE_2 pFile
    )
{
    BOOLEAN bInLock = FALSE;
    BOOLEAN bIsRundown = FALSE;

    LWIO_LOCK_RWMUTEX_EXCLUSIVE(bInLock, &pFile->mutex);

    bIsRundown = SrvFile2IsRundown_inlock(pFile);

    LWIO_UNLOCK_RWMUTEX(bInLock, &pFile->mutex);

    return bIsRundown;
}

static
VOID
SrvFile2Free(
    PLWIO_SRV_FILE_2 pFile
    )
{
    LWIO_LOG_DEBUG( "Freeing file [object:0x%x]"
                    "[fid: (persistent:%08X)(volatile:%08X)]",
                    pFile,
                    pFile->fid.ullPersistentId,
                    pFile->fid.ullVolatileId);

    // Cannot be in the parent since parent would have a reference.
    LWIO_ASSERT(!SrvFile2IsInParent_inlock(pFile));

    if (pFile->pMutex)
    {
        pthread_rwlock_destroy(&pFile->mutex);
        pFile->pMutex = NULL;
    }

    if (pFile->pFilename)
    {
        SRV_FREE_UNICODE_STRING(&pFile->pFilename->Name);
        SrvFreeMemory(pFile->pFilename);
    }

    if (pFile->hOplock)
    {
        SrvReleaseOplock(pFile->hOplock);
        pFile->hOplock = NULL;
    }

    if (pFile->hFile)
    {
        if (pFile->bReleaseOnly)
        {
            IoReleaseFile(pFile->hFile);
        }
        else
        {
            SrvAsyncCloseFileStateExecute(
                &pFile->pAsyncCloseFileState,
                pFile->hFile);
        }
    }

    if (pFile->pAsyncCloseFileState)
    {
        SrvAsyncCloseFileStateFree(pFile->pAsyncCloseFileState);
    }

    SrvFile2UnblockIdleTimeout(pFile);

    if (pFile->pwszFilename)
    {
        SrvFreeMemory(pFile->pwszFilename);
    }

    if (pFile->searchSpace.pwszSearchPattern)
    {
        SrvFreeMemory(pFile->searchSpace.pwszSearchPattern);
    }

    if (pFile->searchSpace.pwszSearchPatternRaw)
    {
        SrvFreeMemory(pFile->searchSpace.pwszSearchPatternRaw);
    }

    if (pFile->searchSpace.pFileInfo)
    {
        SrvFreeMemory(pFile->searchSpace.pFileInfo);
    }

    if (pFile->resource.ulResourceId)
    {
        PSRV_RESOURCE pResource = NULL;

        SrvElementsUnregisterResource(pFile->resource.ulResourceId, &pResource);
        pFile->resource.ulResourceId = 0;
    }

    // Release parent at the end
    if (pFile->pTree)
    {
        SrvTree2Release(pFile->pTree);
    }

    SrvFreeMemory(pFile);
}

static
BOOLEAN
SrvFile2IsRundown_inlock(
    PLWIO_SRV_FILE_2 pFile
    )
{
    return IsSetFlag(pFile->objectFlags, SRV_OBJECT_FLAG_RUNDOWN);
}

static
VOID
SrvFile2SetRundown_inlock(
    PLWIO_SRV_FILE_2 pFile
    )
{
    LWIO_ASSERT(!IsSetFlag(pFile->objectFlags, SRV_OBJECT_FLAG_RUNDOWN));
    SetFlag(pFile->objectFlags, SRV_OBJECT_FLAG_RUNDOWN);
}

