/* -*- 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:
 *
 *        leasekey.c
 *
 * Abstract:
 *
 *        Pvfs Driver Lease Key Table
 *
 * Authors: Gerald Carter <gcarter@likewise.com>
 */

#include "pvfs.h"

#define PVFS_LEASE_KEY_HASH_TABLE_SIZE     1021

static PSMB_HASH_TABLE gPvfsLeaseKeyTable = NULL;
static pthread_rwlock_t gPvfsLeaseKeyTableRwLock;
static pthread_rwlock_t* gpPvfsLeaseKeyTableRwLock = NULL;

static
NTSTATUS
PvfspLeaseKeyAllocate(
    IN OUT PPVFS_LEASE_KEY* ppLeaseKey
    );

static
VOID
PvfspLeaseKeyFree(
    IN OUT PPVFS_LEASE_KEY LeaseKey
    );

static
VOID
PvfspLeaseKeyFreeHashEntry(
    IN OUT const SMB_HASH_ENTRY* Entry
    );

static
size_t
PvfspLeaseKeyHash(
    IN PCVOID Guid
    );

static
LONG
PvfsLeaseKeyCompareGuid(
    IN PGUID Guid1,
    IN PGUID Guid2
    );

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

NTSTATUS
PvfsLeaseKeyTableIntialize(
    VOID
    )
{
    NTSTATUS status = STATUS_SUCCESS;

    // Should only be called once
    LWIO_ASSERT(gPvfsLeaseKeyTable == NULL);

    status = LwErrnoToNtStatus(
                 pthread_rwlock_init(
                     &gPvfsLeaseKeyTableRwLock,
                     NULL));
    BAIL_ON_NT_STATUS(status);

    gpPvfsLeaseKeyTableRwLock = &gPvfsLeaseKeyTableRwLock;

    status = SMBHashCreate(
                 PVFS_LEASE_KEY_HASH_TABLE_SIZE,
                 (SMB_HASH_KEY_COMPARE)PvfsLeaseKeyCompareGuid,
                 (SMB_HASH_KEY)PvfspLeaseKeyHash,
                 PvfspLeaseKeyFreeHashEntry,
                 &gPvfsLeaseKeyTable);
    BAIL_ON_NT_STATUS(status);

error:

    if (!NT_SUCCESS(status))
    {
        PvfsLeaseKeyTableDestroy();
    }

    return status;
}

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

VOID
PvfsLeaseKeyTableDestroy(
    VOID
    )
{
    BOOLEAN tableLocked = FALSE;

    if (gpPvfsLeaseKeyTableRwLock)
    {
        LWIO_LOCK_RWMUTEX_EXCLUSIVE(tableLocked, &gPvfsLeaseKeyTableRwLock);

        SMBHashSafeFree(&gPvfsLeaseKeyTable);

        LWIO_UNLOCK_RWMUTEX(tableLocked, &gPvfsLeaseKeyTableRwLock);

        gpPvfsLeaseKeyTableRwLock = NULL;
        pthread_rwlock_destroy(&gPvfsLeaseKeyTableRwLock);
    }

    return;
}

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

NTSTATUS
PvfsLeaseKeyLookup(
    IN PPVFS_CCB Ccb,
    IN PGUID Guid,
    OUT PPVFS_LEASE_KEY* ppLeaseKey
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    BOOLEAN tableLocked = FALSE;
    PPVFS_LEASE_KEY leaseKey = NULL;

    LWIO_LOCK_RWMUTEX_EXCLUSIVE(tableLocked, &gPvfsLeaseKeyTableRwLock);

    status = SMBHashGetValue(
                 gPvfsLeaseKeyTable,
                 Guid,
                 OUT_PPVOID(&leaseKey));
    switch (status)
    {
        case STATUS_SUCCESS:
            if (Ccb->pScb != leaseKey->pScb)
            {
                // Cannot re-use a lease key for different Streams.
                leaseKey = NULL;

                status = STATUS_INVALID_PARAMETER;
                BAIL_ON_NT_STATUS(status);
            }

            // Take a new reference.
            *ppLeaseKey = PvfsLeaseKeyReference(leaseKey);
            break;

        case STATUS_NOT_FOUND:
            status = PvfspLeaseKeyAllocate(&leaseKey);
            BAIL_ON_NT_STATUS(status);

            LwRtlCopyMemory(&leaseKey->Key, Guid, sizeof(leaseKey->Key));

            leaseKey->pScb = PvfsReferenceSCB(Ccb->pScb);

            status = SMBHashSetValue(
                         gPvfsLeaseKeyTable,
                         &leaseKey->Key,
                         leaseKey);
            BAIL_ON_NT_STATUS(status);

            // Use the initial reference created when the LeaseKey was allocated
            *ppLeaseKey = leaseKey;

            break;

        default:
            break;
    }
    BAIL_ON_NT_STATUS(status);

error:

    LWIO_UNLOCK_RWMUTEX(tableLocked, &gPvfsLeaseKeyTableRwLock);

    if (!NT_SUCCESS(status))
    {
        if (leaseKey)
        {
            PvfspLeaseKeyFree(leaseKey);
        }
    }

    return status;
}

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

static
NTSTATUS
PvfspLeaseKeyAllocate(
    IN OUT PPVFS_LEASE_KEY* ppLeaseKey
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    PPVFS_LEASE_KEY key = NULL;

    status = PvfsAllocateMemory(OUT_PPVOID(&key), sizeof(*key), FALSE);
    BAIL_ON_NT_STATUS(status);

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

    key->pMutex = &key->Mutex;

    key->RefCount = 1;
    key->pScb = NULL;
    key->LeaseState = IO_LEASE_STATE_NONE;

    *ppLeaseKey = key;

error:

    if (!NT_SUCCESS(status))
    {
        PvfspLeaseKeyFree(key);
    }

    return status;
}


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

PPVFS_LEASE_KEY
PvfsLeaseKeyReference(
    IN PPVFS_LEASE_KEY LeaseKey
    )
{
    InterlockedIncrement(&LeaseKey->RefCount);

    return LeaseKey;
}

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

VOID
PvfsLeaseKeyRelease(
    IN OUT PPVFS_LEASE_KEY* ppLeaseKey
    )

{
    NTSTATUS status = STATUS_SUCCESS;
    PPVFS_LEASE_KEY leaseKey = NULL;
    BOOLEAN tableLocked = FALSE;

    LWIO_ASSERT(ppLeaseKey && *ppLeaseKey);

    leaseKey = *ppLeaseKey;

    LWIO_LOCK_RWMUTEX_EXCLUSIVE(tableLocked, &gPvfsLeaseKeyTableRwLock);

    if (InterlockedDecrement(&leaseKey->RefCount) == 0)
    {
        status = SMBHashRemoveKey(gPvfsLeaseKeyTable, &leaseKey->Key);
        LWIO_ASSERT(STATUS_SUCCESS == status);

        // Unneeded since the Hash Table remove called Free()
        // PvfspLeaseKeyFree(leaseKey);
    }

    LWIO_UNLOCK_RWMUTEX(tableLocked, &gPvfsLeaseKeyTableRwLock);

    *ppLeaseKey = NULL;

    return;
}

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

LONG
PvfsLeaseKeyCompare(
    IN PPVFS_LEASE_KEY LeaseKey1,
    IN PPVFS_LEASE_KEY LeaseKey2
    )
{
    LWIO_ASSERT(LeaseKey1 && LeaseKey2);

    return memcmp(&LeaseKey1->Key, &LeaseKey2->Key, sizeof(LeaseKey1->Key));
}

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

static
LONG
PvfsLeaseKeyCompareGuid(
    IN PGUID Guid1,
    IN PGUID Guid2
    )
{
    LWIO_ASSERT(Guid1 && Guid2);

    return memcmp(Guid1, Guid2, sizeof(*Guid1));
}

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

static
VOID
PvfspLeaseKeyFreeHashEntry(
    IN OUT const SMB_HASH_ENTRY* Entry
    )
{
    PvfspLeaseKeyFree((PPVFS_LEASE_KEY)Entry->pValue);

    return;
}


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

static
VOID
PvfspLeaseKeyFree(
    IN OUT PPVFS_LEASE_KEY LeaseKey
    )
{
    LWIO_ASSERT(LeaseKey);

    if (LeaseKey->pScb)
    {
        PvfsReleaseSCB(&LeaseKey->pScb);
    }

    if (LeaseKey->pMutex)
    {
        pthread_mutex_destroy(&LeaseKey->Mutex);
    }

    PvfsFreeMemory(OUT_PPVOID(&LeaseKey));

    return;
}

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

static
size_t
PvfspLeaseKeyHash(
    IN PCVOID Guid
    )
{
    ULONG keyResult = 0;
    PGUID guid = (PGUID)Guid;

    // XOR each 32bit chuck together
    keyResult = guid->Data1 ^
                ((guid->Data2 << 16) | guid->Data3) ^
                ((guid->Data4[0] << 24) |
                 (guid->Data4[1] << 16) |
                 (guid->Data4[2] << 8) |
                 guid->Data4[3]) ^
                ((guid->Data4[4] << 24) |
                 (guid->Data4[5] << 16) |
                 (guid->Data4[6] << 8) |
                 guid->Data4[7]);

    return keyResult;
}


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

NTSTATUS
PvfsEcpGetLeaseKey(
    PPVFS_IRP_CONTEXT pIrpContext,
    PPVFS_CCB pCcb
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    PIRP pIrp = pIrpContext->pIrp;
    ULONG ecpSize = 0;
    PGUID leaseKeyGuid = NULL;
    PPVFS_LEASE_KEY leaseKeyEntry = NULL;

    status = IoRtlEcpListFind(
                    pIrp->Args.Create.EcpList,
                    IO_ECP_TYPE_LEASE_KEY,
                    OUT_PPVOID(&leaseKeyGuid),
                    &ecpSize);
    if (STATUS_SUCCESS != status)
    {
        // Squash error as non-fatal
        status = STATUS_SUCCESS;
        goto error;
    }

    if (ecpSize != sizeof(*leaseKeyGuid))
    {
        status = STATUS_INVALID_PARAMETER;
        BAIL_ON_NT_STATUS(status);
    }

    status = PvfsLeaseKeyLookup(pCcb, leaseKeyGuid, &leaseKeyEntry);
    BAIL_ON_NT_STATUS(status);

    pCcb->LeaseKey = leaseKeyEntry;
    leaseKeyEntry = NULL;

    status = STATUS_SUCCESS;

error:

    if (!NT_SUCCESS(status))
    {
        if (leaseKeyEntry)
        {
            PvfsLeaseKeyRelease(&leaseKeyEntry);
        }
    }

    return status;
}

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

VOID
PvfsLeaseKeySetLeaseState(
    IN PPVFS_LEASE_KEY LeaseKey,
    IN IO_LEASE_STATE LeaseState
    )
{
    BOOLEAN locked = FALSE;

    LWIO_LOCK_MUTEX(locked, &LeaseKey->Mutex);
    LeaseKey->LeaseState = LeaseState;
    LWIO_UNLOCK_MUTEX(locked, &LeaseKey->Mutex);

    return;
}

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

IO_LEASE_STATE
PvfsLeaseKeyGetLeaseState(
    IN PPVFS_LEASE_KEY LeaseKey
    )
{
    BOOLEAN locked = FALSE;
    IO_LEASE_STATE leaseState = IO_LEASE_STATE_NONE;

    LWIO_LOCK_MUTEX(locked, &LeaseKey->Mutex);
    leaseState = LeaseKey->LeaseState;
    LWIO_UNLOCK_MUTEX(locked, &LeaseKey->Mutex);

    return leaseState;
}
