/* -*- 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 (c) 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@likewise.com
 */

/*
 * Copyright (C) Likewise Software. All rights reserved.
 *
 * Module Name:
 *
 *        oplock.c
 *
 * Abstract:
 *
 *        Likewise IO (LWIO) - SRV
 *
 *        Protocols API - SMBV2
 *
 *        SMB2.1 Leases
 *
 * Authors: Gerald Carter <gcarter@likewise.com>
 *
 */

#include "includes.h"

typedef struct _SRV_LEASE_TABLE {
    // Links entry into global hash table of lease tables.
    LW_HASHTABLE_NODE HashNode;
    LONG RefCount;
    pthread_rwlock_t RwLock;
    pthread_rwlock_t* pRwLock;
    PLWRTL_RB_TREE Table;
    uuid_t ClientGUID;
} SRV_LEASE_TABLE;

typedef struct _SRV_LEASE_FILE_IDENTIFIER {
    LW_LIST_LINKS LeaseRecord;
    SMB2_FID FileIdentifier;
} SRV_LEASE_FILE_IDENTIFIER, *PSRV_LEASE_FILE_IDENTIFIER;

static
NTSTATUS
SrvLeaseTableAllocate(
    IN uuid_t *pGUID,
    OUT PSRV_LEASE_TABLE *ppLeaseTable
    );

static
NTSTATUS
SrvLeaseRecordAllocate(
    IN PSRV_LEASE_TABLE LeaseTable,
    IN PGUID ClientLeaseKey,
    OUT PSRV_LEASE_RECORD* pLeaseRecord
    );

static
VOID
SrvLeaseRecordRemove(
    IN PSRV_LEASE_RECORD pLeaseRecord
    );

static
int
SrvLeaseKeyCompare(
    PVOID pKey1,
    PVOID pKey2
    );

static
VOID
SrvLeaseRecordFree(
    IN OUT PVOID Data
    );

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

#define INITIAL_TABLE_SIZE 121

PCVOID
SrvLeaseTableGetKey(
    PLW_HASHTABLE_NODE pNode,
    PVOID pUserData
    )
{
    PSRV_LEASE_TABLE pTable = LW_STRUCT_FROM_FIELD(
                                    pNode,
                                    SRV_LEASE_TABLE,
                                    HashNode);

    return &pTable->ClientGUID;
}

ULONG
SrvLeaseTableDigest(
    PCVOID pKey,
    PVOID pUserData
    )
{
    union GUIDKey {
        ULONG  Longs[4];
        uuid_t Uuid;
    };

    const union GUIDKey *pGUIDKey = (const union GUIDKey *)pKey;

    return (pGUIDKey->Longs[0] ^
            pGUIDKey->Longs[1] ^
            pGUIDKey->Longs[2] ^
            pGUIDKey->Longs[3]);
}

BOOLEAN
SrvLeaseTableEqual(
    PCVOID pKey1,
    PCVOID pKey2,
    PVOID pUserData
    )
{
    return !memcmp(pKey1, pKey2, sizeof(uuid_t));
}

NTSTATUS
SrvLeaseInit(
    PSRV_LEASE_TABLES pTables
    )
{
    NTSTATUS status = STATUS_SUCCESS;

    status = LwErrnoToNtStatus(pthread_mutex_init(&pTables->Mutex, NULL));
    BAIL_ON_NT_STATUS(status);
    pTables->bDestroyMutex = TRUE;

    status = LwErrnoToNtStatus(pthread_cond_init(&pTables->Condition, NULL));
    BAIL_ON_NT_STATUS(status);
    pTables->bDestroyCondition = TRUE;

    pTables->bShuttingDown = FALSE;

    status = LwRtlCreateHashTable(
        &pTables->pTable,
        SrvLeaseTableGetKey,
        SrvLeaseTableDigest,
        SrvLeaseTableEqual,
        pTables,
        INITIAL_TABLE_SIZE);
    BAIL_ON_NT_STATUS(status);

error:

    if (!NT_SUCCESS(status))
    {
        SrvLeaseShutdown(pTables);
    }

    return status;
}

VOID
SrvLeaseShutdown(
    IN OUT PSRV_LEASE_TABLES pTables
    )
{
    BOOLEAN bInLock = FALSE;

    if (pTables->pTable)
    {
        LWIO_LOCK_MUTEX(bInLock, &pTables->Mutex);

        pTables->bShuttingDown = TRUE;
        while (LwRtlHashTableGetCount(pTables->pTable) != 0)
        {
            int error = pthread_cond_wait(&pTables->Condition, &pTables->Mutex);
            LWIO_ASSERT(!error || (error == EINTR));
        }
        LWIO_UNLOCK_MUTEX(bInLock, &pTables->Mutex);

        LwRtlFreeHashTable(&pTables->pTable);
    }

    if (pTables->bDestroyMutex)
    {
        pthread_mutex_destroy(&pTables->Mutex);
        pTables->bDestroyMutex = FALSE;
    }

    if (pTables->bDestroyCondition)
    {
        pthread_cond_destroy(&pTables->Condition);
        pTables->bDestroyCondition = FALSE;
    }
}

static
PSRV_LEASE_TABLES
SrvGetLeaseTables(
    VOID
    )
{
    return &gSrvElements.LeaseTables;
}

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

static
NTSTATUS
SrvLeaseTableFind(
    uuid_t *pGUID,
    PSRV_LEASE_TABLE *ppLeaseTable
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    PSRV_LEASE_TABLES pLeaseTables = SrvGetLeaseTables();
    PSRV_LEASE_TABLE pLeaseTable = NULL;
    PLW_HASHTABLE_NODE pNode = NULL;
    BOOLEAN bTablesLocked = FALSE;
    
    LWIO_LOCK_MUTEX(bTablesLocked, &pLeaseTables->Mutex);
    status = LwRtlHashTableFindKey(pLeaseTables->pTable, &pNode, pGUID);
    if (status == STATUS_SUCCESS)
    {
        pLeaseTable = LW_STRUCT_FROM_FIELD(pNode, SRV_LEASE_TABLE, HashNode);
        if (LwInterlockedIncrement(&pLeaseTable->RefCount) == 1)
        {
            /* Entry is in the process of being freed.  Ignore it. */
            status = STATUS_NOT_FOUND;
            pLeaseTable = NULL;
        }
    }
    LWIO_UNLOCK_MUTEX(bTablesLocked, &pLeaseTables->Mutex);
    BAIL_ON_NT_STATUS(status);

error:

    *ppLeaseTable = pLeaseTable;

    return status;
}

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

NTSTATUS
SrvLeaseTableFindOrAllocate(
    IN uuid_t *pGUID,
    OUT PSRV_LEASE_TABLE *ppLeaseTable
    )
{
    NTSTATUS status = STATUS_SUCCESS;

    status = SrvLeaseTableFind(pGUID, ppLeaseTable);

    if (status == STATUS_NOT_FOUND)
    {
        status = SrvLeaseTableAllocate(pGUID, ppLeaseTable);
    }
    BAIL_ON_NT_STATUS(status);

error:

    return status;
}

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

static
VOID
SrvLeaseTableFree(
    PSRV_LEASE_TABLE pLeaseTable
    )
{

    if (pLeaseTable)
    {
        if (pLeaseTable->Table)
        {
            LwRtlRBTreeFree(pLeaseTable->Table);
        }

        if (pLeaseTable->pRwLock)
        {
            pthread_rwlock_destroy(&pLeaseTable->RwLock);
        }
        SrvFreeMemory(pLeaseTable);
    }
}

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

static
NTSTATUS
SrvLeaseTableAllocate(
    IN uuid_t *pGUID,
    OUT PSRV_LEASE_TABLE *ppLeaseTable
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    PSRV_LEASE_TABLES pLeaseTables = SrvGetLeaseTables();
    PSRV_LEASE_TABLE pOldTable = NULL;
    PSRV_LEASE_TABLE pNewTable = NULL;
    PLW_HASHTABLE_NODE pNode = NULL;
    BOOLEAN bTablesLocked = FALSE;

    status = SrvAllocateMemory(sizeof(*pNewTable), OUT_PPVOID(&pNewTable));
    BAIL_ON_NT_STATUS(status);

    status = LwErrnoToNtStatus(pthread_rwlock_init(&pNewTable->RwLock, NULL));
    BAIL_ON_NT_STATUS(status);

    pNewTable->pRwLock = &pNewTable->RwLock;

    status = LwRtlRBTreeCreate(
                &SrvLeaseKeyCompare,
                NULL,
                &SrvLeaseRecordFree,
                &pNewTable->Table);
    BAIL_ON_NT_STATUS(status);

    pNewTable->RefCount = 1;

    LwRtlCopyMemory(
        &pNewTable->ClientGUID,
        pGUID,
        sizeof(pNewTable->ClientGUID));

    LWIO_LOCK_MUTEX(bTablesLocked, &pLeaseTables->Mutex);
    status = LwRtlHashTableFindKey(pLeaseTables->pTable, &pNode, pGUID);
    if (status == STATUS_SUCCESS)
    {
        pOldTable = LW_STRUCT_FROM_FIELD(pNode, SRV_LEASE_TABLE, HashNode);
        if (LwInterlockedIncrement(&pOldTable->RefCount) > 1)
        {
            // Concurrent allocate of the same node and we lost.
            // Use the winner's node.
            SrvLeaseTableFree(pNewTable);
            pNewTable = pOldTable;
        }
        else
        {
            // Old node is being destroyed
            status = STATUS_NOT_FOUND;
        }
    }

    if (status == STATUS_NOT_FOUND)
    {
        LwRtlHashTableResizeAndInsert(
            pLeaseTables->pTable,
            &pNewTable->HashNode,
            NULL);
        status = STATUS_SUCCESS;
    }

cleanup:

    LWIO_UNLOCK_MUTEX(bTablesLocked, &pLeaseTables->Mutex);
    *ppLeaseTable = pNewTable;
    return status;

error:
    SrvLeaseTableFree(pNewTable);
    pNewTable = NULL;

    goto cleanup;
}

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

static
VOID
SrvLeaseTableRemove(
    IN PSRV_LEASE_TABLE pLeaseTable
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    PSRV_LEASE_TABLES pLeaseTables = SrvGetLeaseTables();
    BOOLEAN bTablesLocked = FALSE;

    LWIO_LOCK_MUTEX(bTablesLocked, &pLeaseTables->Mutex);
    status = LwRtlHashTableRemove(
                    pLeaseTables->pTable,
                    &pLeaseTable->HashNode);
    if (pLeaseTables->bShuttingDown)
    {
        pthread_cond_signal(&pLeaseTables->Condition);
    }
    LWIO_UNLOCK_MUTEX(bTablesLocked, &pLeaseTables->Mutex);
    LWIO_ASSERT(status == STATUS_SUCCESS);
    SrvLeaseTableFree(pLeaseTable);
}

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

VOID
SrvLeaseTableAcquire(
    IN PSRV_LEASE_TABLE pLeaseTable
    )
{
    LWIO_ASSERT(LwInterlockedIncrement(&pLeaseTable->RefCount) > 1);
}

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

VOID
SrvLeaseTableRelease(
    IN PSRV_LEASE_TABLE* ppLeaseTable
    )
{
    PSRV_LEASE_TABLE pLeaseTable = *ppLeaseTable;

    if (pLeaseTable &&
        LwInterlockedDecrement(&pLeaseTable->RefCount) == 0)
    {
        SrvLeaseTableRemove(pLeaseTable);
    }
    *ppLeaseTable = NULL;
}

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

NTSTATUS
SrvLeaseRecordFind(
    IN PLWIO_SRV_CONNECTION pConnection,
    IN PGUID pClientLeaseKey,
    OUT PSRV_LEASE_RECORD *ppLeaseRecord
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    PSRV_LEASE_TABLE pLeaseTable = pConnection->LeaseTable;
    PSRV_LEASE_RECORD pLeaseRecord = NULL;
    BOOLEAN bTableLocked = FALSE;

    if (!pLeaseTable)
    {
        status = SrvLeaseTableFindOrAllocate(
                        &pConnection->clientProperties.GUID,
                        &pConnection->LeaseTable);
        BAIL_ON_NT_STATUS(status);

        pLeaseTable = pConnection->LeaseTable;
    }

    LWIO_LOCK_RWMUTEX_SHARED(bTableLocked, &pLeaseTable->RwLock);
    status = LwRtlRBTreeFind(
                 pLeaseTable->Table,
                 (PVOID)pClientLeaseKey,
                 OUT_PPVOID(&pLeaseRecord));
    if (status == STATUS_SUCCESS &&
        LwInterlockedIncrement(&pLeaseRecord->RefCount) == 1)
    {
        /* Entry is in the process of being freed.  Ignore it. */
        pLeaseRecord = NULL;
        status = STATUS_NOT_FOUND;
    }
    LWIO_UNLOCK_RWMUTEX(bTableLocked, &pLeaseTable->RwLock);
    BAIL_ON_NT_STATUS(status);

error:

    *ppLeaseRecord = pLeaseRecord;

    return status;
}

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

NTSTATUS
SrvLeaseRecordFindOrAllocate(
    IN PLWIO_SRV_CONNECTION pConnection,
    IN PGUID pClientLeaseKey,
    OUT PSRV_LEASE_RECORD *ppLeaseRecord
    )
{
    NTSTATUS status = STATUS_SUCCESS;

    status = SrvLeaseRecordFind(
                    pConnection,
                    pClientLeaseKey,
                    ppLeaseRecord);

    if (status == STATUS_NOT_FOUND)
    {
        status = SrvLeaseRecordAllocate(
                        pConnection->LeaseTable,
                        pClientLeaseKey,
                        ppLeaseRecord);
    }
    BAIL_ON_NT_STATUS(status);

error:

    return status;
}

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

VOID
SrvLeaseRecordAcquire(
    IN PSRV_LEASE_RECORD pLeaseRecord
    )
{
    LwInterlockedIncrement(&pLeaseRecord->RefCount);
}

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

VOID
SrvLeaseRecordRelease(
    IN PSRV_LEASE_RECORD* ppLeaseRecord
    )
{
    PSRV_LEASE_RECORD pLeaseRecord = *ppLeaseRecord;

    if (pLeaseRecord &&
        LwInterlockedDecrement(&pLeaseRecord->RefCount) == 0)
    {
        SrvLeaseRecordRemove(pLeaseRecord);
    }
    *ppLeaseRecord = NULL;
}

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

static
NTSTATUS
SrvLeaseRecordAllocate(
    IN PSRV_LEASE_TABLE pLeaseTable,
    IN PGUID pClientLeaseKey,
    OUT PSRV_LEASE_RECORD* ppLeaseRecord
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    PSRV_LEASE_RECORD pOldRecord = NULL;
    PSRV_LEASE_RECORD pNewRecord = NULL;
    uuid_t uuidKey = { 0 };
    BOOLEAN bTableLocked = FALSE;

    LWIO_ASSERT(pLeaseTable);

    status = SrvAllocateMemory(sizeof(*pNewRecord), OUT_PPVOID(&pNewRecord));
    BAIL_ON_NT_STATUS(status);

    status = LwErrnoToNtStatus(pthread_mutex_init(&pNewRecord->Mutex, NULL));
    BAIL_ON_NT_STATUS(status);

    pNewRecord->pLeaseTable = pLeaseTable;
    pNewRecord->pMutex = &pNewRecord->Mutex;
    LwListInit(&pNewRecord->OplockList);
    LwListInit(&pNewRecord->IterList);
    pNewRecord->RefCount = 1;

    uuid_generate(uuidKey);

    LwRtlCopyMemory(
        &pNewRecord->FileSystemLeaseKey,
        uuidKey,
        sizeof(pNewRecord->FileSystemLeaseKey));

    LwRtlCopyMemory(
        &pNewRecord->ClientLeaseKey,
        pClientLeaseKey,
        sizeof(pNewRecord->ClientLeaseKey));

    LWIO_LOCK_RWMUTEX_EXCLUSIVE(bTableLocked, &pLeaseTable->RwLock);
    status = LwRtlRBTreeFind(
                 pLeaseTable->Table,
                 pClientLeaseKey,
                 OUT_PPVOID(&pOldRecord));
    if (status == STATUS_SUCCESS &&
        LwInterlockedIncrement(&pOldRecord->RefCount) > 1)
    {
        // Concurrent allocate of the same node and we lost.
        // Use the winner's node.
        SrvLeaseRecordFree(pNewRecord);
        pNewRecord = pOldRecord;
    }
    else
    {
        status = LwRtlRBTreeAdd(
                     pLeaseTable->Table,
                     &pNewRecord->ClientLeaseKey,
                     pNewRecord);
        BAIL_ON_NT_STATUS(status);

        SrvLeaseTableAcquire(pLeaseTable);
    }

cleanup:

    LWIO_UNLOCK_RWMUTEX(bTableLocked, &pLeaseTable->RwLock);
    *ppLeaseRecord = pNewRecord;
    return status;

error:
    SrvLeaseRecordFree(pNewRecord);
    pNewRecord = NULL;

    goto cleanup;
}

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

static
VOID
SrvLeaseRecordRemove(
    IN PSRV_LEASE_RECORD pLeaseRecord
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    PSRV_LEASE_TABLE pLeaseTable = pLeaseRecord->pLeaseTable;
    BOOLEAN bTableLocked = FALSE;

    LWIO_LOCK_RWMUTEX_EXCLUSIVE(bTableLocked, &pLeaseTable->RwLock);
    status = LwRtlRBTreeRemove(
                    pLeaseTable->Table,
                    &pLeaseRecord->ClientLeaseKey);
    LWIO_UNLOCK_RWMUTEX(bTableLocked, &pLeaseTable->RwLock);
    LWIO_ASSERT(status == STATUS_SUCCESS);

    SrvLeaseTableRelease(&pLeaseTable);
}

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

NTSTATUS
SrvLeaseRecordAddOplock(
    IN PSRV_LEASE_RECORD pLeaseRecord,
    IN HANDLE hOplock
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    PSRV_OPLOCK pOplock = hOplock;
    BOOLEAN bLeaseLocked = FALSE;

    if (LwInterlockedIncrement(&pLeaseRecord->RefCount) <= 1)
    {
        status = STATUS_INTERNAL_ERROR;
        BAIL_ON_NT_STATUS(status);
    }
    LWIO_ASSERT(pOplock->pLeaseRecord == NULL);
    pOplock->pLeaseRecord = pLeaseRecord;

    LWIO_LOCK_MUTEX(bLeaseLocked, &pLeaseRecord->Mutex);
    LwListInsertTail(&pLeaseRecord->OplockList, &pOplock->LeaseRecordEntry);
    LWIO_UNLOCK_MUTEX(bLeaseLocked, &pLeaseRecord->Mutex);

cleanup:

    LWIO_UNLOCK_MUTEX(bLeaseLocked, &pLeaseRecord->Mutex);

    return status;

error:

    goto cleanup;
}

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

static
VOID
SrvLeaseRecordUpdateIterators(
    PSRV_LEASE_RECORD pLeaseRecord,
    PLW_LIST_ENTRY pRemovingEntry
    )
{
    PLW_LIST_LINKS cursor = NULL;
    PSRV_LEASE_RECORD_ITERATOR pIter = NULL;
    PLW_LIST_ENTRY pNextEntry = LwListTraverse(
                                    &pLeaseRecord->OplockList,
                                    pRemovingEntry);

    while ((cursor = LwListTraverse(&pLeaseRecord->IterList, cursor)) != NULL)
    {
        pIter = LW_STRUCT_FROM_FIELD(
                    cursor,
                    SRV_LEASE_RECORD_ITERATOR,
                    LeaseRecordEntry);
        if (pIter->pNextEntry == pRemovingEntry)
        {
            pIter->pNextEntry = pNextEntry;
        }
    }
}

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

VOID
SrvLeaseRecordRemoveOplock(
    IN HANDLE hOplock
    )
{
    PSRV_OPLOCK pOplock = hOplock;
    PSRV_LEASE_RECORD pLeaseRecord = pOplock->pLeaseRecord;
    BOOLEAN bLeaseLocked = FALSE;

    if (pLeaseRecord)
    {
        LWIO_LOCK_MUTEX(bLeaseLocked, &pLeaseRecord->Mutex);
        SrvLeaseRecordUpdateIterators(pLeaseRecord, &pOplock->LeaseRecordEntry);
        LwListRemove(&pOplock->LeaseRecordEntry);
        LWIO_UNLOCK_MUTEX(bLeaseLocked, &pLeaseRecord->Mutex);
        SrvLeaseRecordRelease(&pLeaseRecord);
        pOplock->pLeaseRecord = NULL;
    }
}

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

static
NTSTATUS
SrvLeaseRecordAddIter(
    IN PSRV_LEASE_RECORD pLeaseRecord,
    IN PSRV_LEASE_RECORD_ITERATOR pIter
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    BOOLEAN bLeaseLocked = FALSE;

    if (LwInterlockedIncrement(&pLeaseRecord->RefCount) <= 1)
    {
        status = STATUS_INTERNAL_ERROR;
        BAIL_ON_NT_STATUS(status);
    }
    LWIO_ASSERT(pIter->pLeaseRecord == NULL);
    pIter->pLeaseRecord = pLeaseRecord;

    LWIO_LOCK_MUTEX(bLeaseLocked, &pLeaseRecord->Mutex);
    LwListInsertTail(&pLeaseRecord->IterList, &pIter->LeaseRecordEntry);
    pIter->pNextEntry = pLeaseRecord->OplockList.Next;
    LWIO_UNLOCK_MUTEX(bLeaseLocked, &pLeaseRecord->Mutex);

cleanup:

    LWIO_UNLOCK_MUTEX(bLeaseLocked, &pLeaseRecord->Mutex);

    return status;

error:

    goto cleanup;
}

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

static
VOID
SrvLeaseRecordRemoveIter(
    IN PSRV_LEASE_RECORD_ITERATOR pIter
    )
{
    BOOLEAN bLeaseLocked = FALSE;

    if (pIter->pLeaseRecord)
    {
        LWIO_LOCK_MUTEX(bLeaseLocked, &pIter->pLeaseRecord->Mutex);
        LwListRemove(&pIter->LeaseRecordEntry);
        LWIO_UNLOCK_MUTEX(bLeaseLocked, &pIter->pLeaseRecord->Mutex);
        SrvLeaseRecordRelease(&pIter->pLeaseRecord);
    }
}

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

NTSTATUS
SrvLeaseRecordIterCreate(
    IN PSRV_LEASE_RECORD pLeaseRecord,
    IN OUT PSRV_LEASE_RECORD_ITERATOR* ppIter
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    PSRV_LEASE_RECORD_ITERATOR pIter = NULL;

    status = SrvAllocateMemory(sizeof(*pIter), OUT_PPVOID(&pIter));
    BAIL_ON_NT_STATUS(status);

    SrvLeaseRecordAddIter(pLeaseRecord, pIter);

error:

    *ppIter = pIter;

    return status;
}

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

VOID
SrvLeaseRecordIterFree(
    PSRV_LEASE_RECORD_ITERATOR* ppIter
    )
{
    PSRV_LEASE_RECORD_ITERATOR pIter = *ppIter;
    
    if (pIter)
    {
        if (pIter->hCurOplock)
        {
            SrvReleaseOplock(pIter->hCurOplock);
            pIter->hCurOplock = NULL;
        }
        SrvLeaseRecordRemoveIter(pIter);
        SrvFreeMemory(pIter);
        *ppIter = NULL;
    }
}

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

VOID
SrvLeaseRecordIterReset(
    PSRV_LEASE_RECORD_ITERATOR pIter
    )
{
    BOOLEAN bLeaseLocked = FALSE;

    if (pIter->hCurOplock)
    {
        SrvReleaseOplock(pIter->hCurOplock);
        pIter->hCurOplock = NULL;
    }
    LWIO_LOCK_MUTEX(bLeaseLocked, &pIter->pLeaseRecord->Mutex);
    pIter->pNextEntry = pIter->pLeaseRecord->OplockList.Next;
    LWIO_UNLOCK_MUTEX(bLeaseLocked, &pIter->pLeaseRecord->Mutex);
}

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

HANDLE
SrvLeaseRecordIterNext(
    PSRV_LEASE_RECORD_ITERATOR pIter
    )
{
    PSRV_OPLOCK pOplock = NULL;
    BOOLEAN bLeaseLocked = FALSE;

    if (pIter->hCurOplock)
    {
        SrvReleaseOplock(pIter->hCurOplock);
        pIter->hCurOplock = NULL;
    }
    
    LWIO_LOCK_MUTEX(bLeaseLocked, &pIter->pLeaseRecord->Mutex);
    while (pIter->pNextEntry != NULL)
    {
        pOplock = LW_STRUCT_FROM_FIELD(
                        pIter->pNextEntry,
                        SRV_OPLOCK,
                        LeaseRecordEntry);
        pIter->pNextEntry = LwListTraverse(
                                &pIter->pLeaseRecord->OplockList,
                                pIter->pNextEntry);
        if (LwInterlockedIncrement(&pOplock->refCount) > 1)
        {
            pIter->hCurOplock = pOplock;
            break;
        }
    }
    LWIO_UNLOCK_MUTEX(bLeaseLocked, &pIter->pLeaseRecord->Mutex);

    return (pIter->hCurOplock);
}

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

static
VOID
SrvLeaseRecordFree(
    IN OUT PVOID Data
    )
{
    PSRV_LEASE_RECORD pLeaseRecord = Data;

    if (pLeaseRecord)
    {
        LWIO_ASSERT(LwListIsEmpty(&pLeaseRecord->OplockList));
        LWIO_ASSERT(LwListIsEmpty(&pLeaseRecord->IterList));

        if (pLeaseRecord->pMutex)
        {
            pthread_mutex_destroy(&pLeaseRecord->Mutex);
            pLeaseRecord->pMutex = NULL;
        }

        RTL_FREE(&pLeaseRecord);
    }
}


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

static
int
SrvLeaseKeyCompare(
    PVOID pKey1,
    PVOID pKey2
    )
{
    return memcmp(pKey1, pKey2, sizeof(GUID));
}

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

BOOLEAN
SrvIsLeaseKeyEqual(
    IN PGUID LeaseKey1,
    IN PGUID LeaseKey2
    )
{
    return (SrvLeaseKeyCompare((PVOID)LeaseKey1, (PVOID)LeaseKey2) == 0);
}

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

IO_LEASE_STATE
SrvLeaseMapSmb2ToIoLeaseState(
    IN ULONG Smb2LeaseState
    )
{
    IO_LEASE_STATE ioLeaseState = 0;

    if (IsSetFlag(Smb2LeaseState, SMB2_LEASE_READ_CACHING))
    {
        ioLeaseState |= IO_LEASE_STATE_READ;
    }

    if (IsSetFlag(Smb2LeaseState, SMB2_LEASE_WRITE_CACHING))
    {
        ioLeaseState |= IO_LEASE_STATE_WRITE;
    }

    if (IsSetFlag(Smb2LeaseState, SMB2_LEASE_HANDLE_CACHING))
    {
        ioLeaseState |= IO_LEASE_STATE_HANDLE;
    }

    return ioLeaseState;
}

ULONG
SrvLeaseMapIoToSmb2LeaseState(
    IN IO_LEASE_STATE IoLeaseState
    )
{
    ULONG smb2LeaseState = 0;

    if (IsSetFlag(IoLeaseState, IO_LEASE_STATE_READ))
    {
        smb2LeaseState |= SMB2_LEASE_READ_CACHING;
    }

    if (IsSetFlag(IoLeaseState, IO_LEASE_STATE_WRITE))
    {
        smb2LeaseState |= SMB2_LEASE_WRITE_CACHING;
    }

    if (IsSetFlag(IoLeaseState, IO_LEASE_STATE_HANDLE))
    {
        smb2LeaseState |= SMB2_LEASE_HANDLE_CACHING;
    }

    return smb2LeaseState;
}
