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

#include "includes.h"

static
PVOID
SrvTimerMain(
    IN  PVOID pData
    );

static
NTSTATUS
SrvTimerGetNextRequest_inlock(
    IN  PSRV_TIMER_CONTEXT  pContext,
    OUT PSRV_TIMER_REQUEST* ppTimerRequest
    );

static
NTSTATUS
SrvTimerDetachRequest_inlock(
    IN OUT PSRV_TIMER_CONTEXT pContext,
    IN OUT PSRV_TIMER_REQUEST pTimerRequest
    );

static
VOID
SrvTimerFree(
    IN  PSRV_TIMER_REQUEST pTimerRequest
    );

static
VOID
SrvTimerStop(
    IN  PSRV_TIMER_CONTEXT pContext
    );

NTSTATUS
SrvTimerInit(
    IN  PSRV_TIMER pTimer
    )
{
    NTSTATUS status = STATUS_SUCCESS;

    memset(&pTimer->context, 0, sizeof(pTimer->context));
    LwListInit(&pTimer->context.Requests);

    pthread_mutex_init(&pTimer->context.mutex, NULL);
    pTimer->context.pMutex = &pTimer->context.mutex;

    pthread_cond_init(&pTimer->context.event, NULL);
    pTimer->context.pEvent = &pTimer->context.event;

    pTimer->context.bStop = FALSE;

    status = pthread_create(
                    &pTimer->timerThread,
                    NULL,
                    &SrvTimerMain,
                    &pTimer->context);
    BAIL_ON_NT_STATUS(status);

    pTimer->pTimerThread = &pTimer->timerThread;

error:

    return status;
}

static
PVOID
SrvTimerMain(
    IN  PVOID pData
    )
{
    NTSTATUS status = 0;
    PSRV_TIMER_CONTEXT pContext = (PSRV_TIMER_CONTEXT)pData;
    PSRV_TIMER_REQUEST pTimerRequest = NULL;
    LONG64 llCurTime = 0LL;
    BOOLEAN bInLock = FALSE;

    LWIO_LOG_DEBUG("Srv timer starting");

    LWIO_LOCK_MUTEX(bInLock, &pContext->mutex);

    while (!pContext->bStop)
    {
        struct timespec tsWakeTime;

        status = SrvTimerGetNextRequest_inlock(pContext, &pTimerRequest);
        if (status == STATUS_SUCCESS)
        {
            status = WireGetCurrentNTTime(&llCurTime);
            BAIL_ON_NT_STATUS(status);

            if (llCurTime >= pTimerRequest->llExpiry)
            {
                SrvTimerDetachRequest_inlock(pContext, pTimerRequest);
                LWIO_ASSERT(!pTimerRequest->bCanceled);

                LWIO_UNLOCK_MUTEX(bInLock, &pContext->mutex);

                pTimerRequest->pfnTimerExpiredCB(
                                    pTimerRequest,
                                    pTimerRequest->pUserData);

                LWIO_LOCK_MUTEX(bInLock, &pContext->mutex);

                SrvTimerRelease(&pTimerRequest);
                continue;
            }
            else
            {
                status = WireNTTimeToTimeSpec(
                                pTimerRequest->llExpiry,
                                &tsWakeTime);
                BAIL_ON_NT_STATUS(status);

                SrvTimerRelease(&pTimerRequest);
            }
        }
        else
        {
            // If the queue is empty wait for a day or until a request arrives
            tsWakeTime.tv_sec = time(NULL) + 86400;
            tsWakeTime.tv_nsec = 0;
        }

        pthread_cond_timedwait(&pContext->event, &pContext->mutex, &tsWakeTime);
    }

cleanup:

    LWIO_UNLOCK_MUTEX(bInLock, &pContext->mutex);

    if (pTimerRequest)
    {
        SrvTimerRelease(&pTimerRequest);
    }

    LWIO_LOG_DEBUG("Srv timer stopping");

    return NULL;

error:

    LWIO_LOG_ERROR("Srv timer stopping due to error [%d]", status);

    goto cleanup;
}

static
NTSTATUS
SrvTimerGetNextRequest_inlock(
    IN  PSRV_TIMER_CONTEXT  pContext,
    OUT PSRV_TIMER_REQUEST* ppTimerRequest
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    PSRV_TIMER_REQUEST pTimerRequest = NULL;
    PLW_LIST_ENTRY pEntry = NULL;

    pEntry = LwListTraverse(&pContext->Requests, NULL);
    if (pEntry == NULL)
    {
        status = STATUS_NOT_FOUND;
        BAIL_ON_NT_STATUS(status);
    }

    pTimerRequest = LW_STRUCT_FROM_FIELD(
                        pEntry,
                        SRV_TIMER_REQUEST,
                        TimerLinks);

    InterlockedIncrement(&pTimerRequest->refCount);

    *ppTimerRequest = pTimerRequest;

cleanup:

    return status;

error:

    *ppTimerRequest = NULL;

    goto cleanup;
}

static
NTSTATUS
SrvTimerDetachRequest_inlock(
    IN OUT PSRV_TIMER_CONTEXT pContext,
    IN OUT PSRV_TIMER_REQUEST pTimerRequest
    )
{
    LWIO_ASSERT(LwListElementIsLinked(&pTimerRequest->TimerLinks));
    LwListRemove(&pTimerRequest->TimerLinks);

    // Release timer queue's reference.
    InterlockedDecrement(&pTimerRequest->refCount);

    return STATUS_SUCCESS;
}

NTSTATUS
SrvTimerPostRequestSpecific(
    IN  PSRV_TIMER             pTimer,
    IN  LONG64                 llExpiry,
    IN  PVOID                  pUserData,
    IN  PFN_SRV_TIMER_CALLBACK pfnTimerExpiredCB,
    OUT PSRV_TIMER_REQUEST*    ppTimerRequest
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    PSRV_TIMER_CONTEXT pTCtx = &pTimer->context;
    PSRV_TIMER_REQUEST pTimerRequest = NULL;
    PLW_LIST_ENTRY pCursor = NULL;
    BOOLEAN bInLock = FALSE;
    BOOLEAN bWakeTimerThread = FALSE;

    if (!llExpiry)
    {
        status = STATUS_INVALID_PARAMETER_1;
        BAIL_ON_NT_STATUS(status);
    }
    if (!pfnTimerExpiredCB)
    {
        status = STATUS_INVALID_PARAMETER_3;
        BAIL_ON_NT_STATUS(status);
    }

    status = SrvAllocateMemory(
                    sizeof(SRV_TIMER_REQUEST),
                    (PVOID*)&pTimerRequest);
    BAIL_ON_NT_STATUS(status);

    LwListInit(&pTimerRequest->TimerLinks);

    // 1 for timer queue, 1 for caller
    pTimerRequest->refCount = 2;

    pTimerRequest->llExpiry = llExpiry;
    pTimerRequest->pUserData = pUserData;
    pTimerRequest->pfnTimerExpiredCB = pfnTimerExpiredCB;
    pTimerRequest->bCanceled = FALSE;

    LWIO_LOCK_MUTEX(bInLock, &pTCtx->mutex);

    while ((pCursor = LwListTraverseReverse(&pTCtx->Requests, pCursor)) != NULL)
    {
        PSRV_TIMER_REQUEST pListRequest = LW_STRUCT_FROM_FIELD(
                                                pCursor,
                                                SRV_TIMER_REQUEST,
                                                TimerLinks);
        if (pListRequest->llExpiry <= llExpiry)
        {
            break;
        }
    }

    if (pCursor == NULL)
    {
        LwListInsertHead(&pTCtx->Requests, &pTimerRequest->TimerLinks);
        bWakeTimerThread = TRUE;
    }
    else
    {
        LwListInsertAfter(pCursor, &pTimerRequest->TimerLinks);
    }

cleanup:

    LWIO_UNLOCK_MUTEX(bInLock, &pTimer->context.mutex);

    if (bWakeTimerThread)
    {
        pthread_cond_signal(&pTimer->context.event);
    }

    *ppTimerRequest = pTimerRequest;

    return status;

error:

    if (pTimerRequest)
    {
        SrvTimerFree(pTimerRequest);
        pTimerRequest = NULL;
    }

    goto cleanup;
}

NTSTATUS
SrvTimerCancelRequestSpecific(
    IN  PSRV_TIMER         pTimer,
    IN  PSRV_TIMER_REQUEST pTimerRequest,
    OUT OPTIONAL PVOID*    ppUserData
    )
{
    PSRV_TIMER_CONTEXT pContext  = &pTimer->context;
    PVOID              pUserData = NULL;
    NTSTATUS           status    = STATUS_SUCCESS;
    BOOLEAN            bInLock   = FALSE;

    LWIO_LOCK_MUTEX(bInLock, &pContext->mutex);

    if (LwListElementIsLinked(&pTimerRequest->TimerLinks))
    {
        LWIO_ASSERT(!pTimerRequest->bCanceled);

        pTimerRequest->pfnTimerExpiredCB = NULL;
        pUserData = pTimerRequest->pUserData;
        pTimerRequest->bCanceled = TRUE;

        SrvTimerDetachRequest_inlock(pContext, pTimerRequest);
    }
    else
    {
        status = STATUS_NOT_FOUND;
        BAIL_ON_NT_STATUS(status);
    }

    LWIO_UNLOCK_MUTEX(bInLock, &pContext->mutex);

cleanup:

    if (ppUserData)
    {
        *ppUserData = pUserData;
    }

    LWIO_UNLOCK_MUTEX(bInLock, &pContext->mutex);

    return status;

error:

    goto cleanup;
}

NTSTATUS
SrvTimerRelease(
    IN  PSRV_TIMER_REQUEST* ppTimerRequest
    )
{
    PSRV_TIMER_REQUEST pTimerRequest = *ppTimerRequest;

    *ppTimerRequest = NULL;
    if (InterlockedDecrement(&pTimerRequest->refCount) == 0)
    {
        SrvTimerFree(pTimerRequest);
    }

    return STATUS_SUCCESS;
}

static
VOID
SrvTimerFree(
    IN  PSRV_TIMER_REQUEST pTimerRequest
    )
{
    SrvFreeMemory(pTimerRequest);
}

VOID
SrvTimerShutdown(
    IN  PSRV_TIMER pTimer
    )
{
    LW_LIST_ENTRY *pEntry = NULL;

    if (pTimer->pTimerThread)
    {
        SrvTimerStop(&pTimer->context);

        pthread_join(pTimer->timerThread, NULL);
    }

    if (pTimer->context.pEvent)
    {
        pthread_cond_destroy(&pTimer->context.event);
        pTimer->context.pEvent = NULL;
    }

    while ((pEntry = LwListTraverse(&pTimer->context.Requests, NULL)) != NULL)
    {
        PSRV_TIMER_REQUEST pRequest = LW_STRUCT_FROM_FIELD(
                                            pEntry,
                                            SRV_TIMER_REQUEST,
                                            TimerLinks);
        SrvTimerDetachRequest_inlock(&pTimer->context, pRequest);
    }

    if (pTimer->context.pMutex)
    {
        pthread_mutex_destroy(&pTimer->context.mutex);
        pTimer->context.pMutex = NULL;
    }
}

static
VOID
SrvTimerStop(
    IN  PSRV_TIMER_CONTEXT pContext
    )
{
    pthread_mutex_lock(&pContext->mutex);
    pContext->bStop = TRUE;
    pthread_mutex_unlock(&pContext->mutex);

    pthread_cond_signal(&pContext->event);
}
