/*
 * 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@likewisesoftware.com
 */

/*
 * Module Name:
 *
 *        watchdog.c
 *
 * Abstract:
 *
 *        Watchdog logic
 *
 * Authors: Brian Koropoff (bkoropoff@likewise.com)
 *
 */

#include "includes.h"

static SM_WATCHDOG gWatchdog =
{
    .Lock = PTHREAD_MUTEX_INITIALIZER,
    .Event = PTHREAD_COND_INITIALIZER,
};

static
PCSTR
ErrorName(
    DWORD Error
    )
{
    PCSTR pName = LwWin32ExtErrorToName(Error);

    return pName ? pName : "UNKNOWN";
}

static
VOID
LwSmWatchdog(
    PLW_WORK_ITEM pItem,
    PVOID pContext
    )
{
    DWORD error = ERROR_SUCCESS;
    PSM_WATCHDOG pWatchdog = pContext;
    PWSTR* ppNames = NULL;
    PWSTR* ppDeps = NULL;
    PSM_TABLE_ENTRY pEntry = NULL;
    PSM_TABLE_ENTRY pDep = NULL;
    BOOLEAN locked = FALSE;
    DWORD serviceIndex = 0;
    DWORD depIndex = 0;
    LW_SERVICE_STATUS status = {0};
    time_t now = 0;
    PSTR pServiceName = NULL;
    DWORD restartAttempts = 0;

    now = time(NULL);
    if (now == (time_t) -1)
    {
        error = LwErrnoToWin32Error(errno);
        BAIL_ON_ERROR(error);
    }

    error = LwSmTableEnumerateEntries(&ppNames);
    BAIL_ON_ERROR(error);

    for (serviceIndex = 0; ppNames[serviceIndex]; serviceIndex++)
    {
        error = LwSmTableGetEntry(ppNames[serviceIndex], &pEntry);
        if (error)
        {
            continue;
        }

        error = LwSmTableGetEntryStatus(pEntry, &status);
        if (error)
        {
            continue;
        }

        if (status.state == LW_SERVICE_STATE_DEAD)
        {
            LOCK(locked, &pEntry->lock);
            if (pEntry->StartAttempts >= 1 &&
                (now - pEntry->LastRestartPeriod) > gLwsmState.RestartPeriod)
            {
                pEntry->StartAttempts = 1;
                pEntry->LastRestartPeriod = now;
            }

            if (pEntry->StartAttempts > 0 && pEntry->StartAttempts < gLwsmState.RestartLimit + 1)
            {
                restartAttempts = pEntry->StartAttempts;
                error = LwSmCopyStringList(pEntry->pInfo->ppwszDependencies, &ppDeps);
                if (!error)
                {
                    error = LwWc16sToMbs(pEntry->pInfo->pwszName, &pServiceName);
                }
            }

            UNLOCK(locked, &pEntry->lock);
            BAIL_ON_ERROR(error);
        }

        if (ppDeps)
        {
            for (depIndex = 0; ppDeps[depIndex]; depIndex++)
            {
                error = LwSmTableGetEntry(ppDeps[depIndex], &pDep);
                if (error)
                {
                    break;
                }

                error = LwSmTableGetEntryStatus(pDep, &status);
                if (error)
                {
                    break;
                }

                LwSmTableReleaseEntry(pDep);
                pDep = NULL;

                if (status.state != LW_SERVICE_STATE_RUNNING)
                {
                    break;
                }
            }

            if (ppDeps[depIndex] == NULL)
            {
                SM_LOG_WARNING(
                        "Respawning dead service: %s (attempt %u/%u)",
                        pServiceName,
                        (unsigned int) restartAttempts,
                        (unsigned int) gLwsmState.RestartLimit);

                error = LwSmTableStartEntry(pEntry, TRUE);
                if (error)
                {
                    SM_LOG_WARNING(
                            "Failed to respawn service: %s (%s)",
                            pServiceName,
                            ErrorName(error));
                }

                LW_SAFE_FREE_MEMORY(pServiceName);
            }

            LwSmFreeStringList(ppDeps);
            ppDeps = NULL;
        }

        LwSmTableReleaseEntry(pEntry);
        pEntry = NULL;
    }

    LOCK(locked, &pWatchdog->Lock);
    if (pWatchdog->Poked)
    {
        pWatchdog->Poked = FALSE;
        LwRtlScheduleWorkItem(pItem, 0);
    }
    else
    {
        pWatchdog->pItem = pItem;
        pthread_cond_signal(&pWatchdog->Event);
    }
    UNLOCK(locked, &pWatchdog->Lock);

error:

    if (pEntry)
    {
        LwSmTableReleaseEntry(pEntry);
    }

    if (pDep)
    {
        LwSmTableReleaseEntry(pDep);
    }

    if (ppNames)
    {
        LwSmFreeStringList(ppNames);
    }

    if (ppDeps)
    {
        LwSmFreeStringList(ppDeps);
    }

    LW_SAFE_FREE_MEMORY(pServiceName);

    return;
}

VOID
LwSmPokeWatchdog(
    VOID
    )
{
    BOOLEAN bLocked = FALSE;

    if (!gLwsmState.bWatchdog)
    {
        return;
    }

    // Unleash the hounds
    LOCK(bLocked, &gWatchdog.Lock);
    if (gWatchdog.Ready)
    {
        if (gWatchdog.pItem)
        {
            LwRtlScheduleWorkItem(gWatchdog.pItem, 0);
            gWatchdog.pItem = NULL;
        }
        else
        {
            gWatchdog.Poked = TRUE;
        }
    }
    UNLOCK(bLocked, &gWatchdog.Lock);
}

DWORD
LwSmWatchdogInit(
    VOID
    )
{
    DWORD error = ERROR_SUCCESS;

    error = LwNtStatusToWin32Error(
            LwRtlCreateWorkItem(
                    gLwsmState.pPool,
                    (PLW_WORK_ITEM*) &gWatchdog.pItem,
                    LwSmWatchdog,
                    &gWatchdog));
    BAIL_ON_ERROR(error);

    gWatchdog.Ready = TRUE;

error:

    return error;
}

VOID
LwSmWatchdogShutdown(
    VOID
    )
{
    BOOL bLocked = FALSE;

    if (gWatchdog.Ready)
    {
        LOCK(bLocked, &gWatchdog.Lock);
        gWatchdog.Ready = FALSE;
        gWatchdog.Poked = FALSE;
        while (!gWatchdog.pItem)
        {
            pthread_cond_wait(&gWatchdog.Event, &gWatchdog.Lock);
        }
        LwRtlFreeWorkItem((PLW_WORK_ITEM*) &gWatchdog.pItem);
        UNLOCK(bLocked, &gWatchdog.Lock);
    }
}
