/*
 * snmp_alarm.c:
 */
/* Portions of this file are subject to the following copyright(s).  See
 * the Net-SNMP's COPYING file for more details and other copyrights
 * that may apply:
 */
/*
 * Portions of this file are copyrighted by:
 * Copyright © 2003 Sun Microsystems, Inc. All rights reserved.
 * Use is subject to license terms specified in the COPYING file
 * distributed with the Net-SNMP package.
 */
/** @defgroup snmp_alarm  generic library based alarm timers for various parts of an application 
 *  @ingroup library
 * 
 *  @{
 */
#include <net-snmp/net-snmp-config.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <signal.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#include <sys/types.h>
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif

#ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# ifdef HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

#include <net-snmp/types.h>
#include <net-snmp/output_api.h>
#include <net-snmp/config_api.h>
#include <net-snmp/utilities.h>

#include <net-snmp/library/snmp_api.h>
#include <net-snmp/library/callback.h>
#include <net-snmp/library/snmp_alarm.h>

static struct snmp_alarm *thealarms = NULL;
static int      start_alarms = 0;
static unsigned int regnum = 1;

int
init_alarm_post_config(int majorid, int minorid, void *serverarg,
                       void *clientarg)
{
    start_alarms = 1;
    set_an_alarm();
    return SNMPERR_SUCCESS;
}

void
init_snmp_alarm(void)
{
    start_alarms = 0;
    snmp_register_callback(SNMP_CALLBACK_LIBRARY,
                           SNMP_CALLBACK_POST_READ_CONFIG,
                           init_alarm_post_config, NULL);
}

void
sa_update_entry(struct snmp_alarm *a)
{
    if (!timerisset(&a->t_lastM)) {
        /*
         * First call of sa_update_entry() for alarm a: set t_lastM and t_nextM.
         */
        netsnmp_get_monotonic_clock(&a->t_lastM);
        NETSNMP_TIMERADD(&a->t_lastM, &a->t, &a->t_nextM);
    } else if (!timerisset(&a->t_nextM)) {
        /*
         * We've been called but not reset for the next call.  
         */
        if (a->flags & SA_REPEAT) {
            if (timerisset(&a->t)) {
                NETSNMP_TIMERADD(&a->t_lastM, &a->t, &a->t_nextM);
            } else {
                DEBUGMSGTL(("snmp_alarm",
                            "update_entry: illegal interval specified\n"));
                snmp_alarm_unregister(a->clientreg);
            }
        } else {
            /*
             * Single time call, remove it.  
             */
            snmp_alarm_unregister(a->clientreg);
        }
    }
}

/**
 * This function removes the callback function from a list of registered
 * alarms, unregistering the alarm.
 *
 * @param clientreg is a unique unsigned integer representing a registered
 *	alarm which the client wants to unregister.
 *
 * @return void
 *
 * @see snmp_alarm_register
 * @see snmp_alarm_register_hr
 * @see snmp_alarm_unregister_all
 */
void
snmp_alarm_unregister(unsigned int clientreg)
{
    struct snmp_alarm *sa_ptr, **prevNext = &thealarms;

    for (sa_ptr = thealarms;
         sa_ptr != NULL && sa_ptr->clientreg != clientreg;
         sa_ptr = sa_ptr->next) {
        prevNext = &(sa_ptr->next);
    }

    if (sa_ptr != NULL) {
        *prevNext = sa_ptr->next;
        DEBUGMSGTL(("snmp_alarm", "unregistered alarm %d\n", 
		    sa_ptr->clientreg));
        /*
         * Note: do not free the clientarg, it's the client's responsibility 
         */
        free(sa_ptr);
    } else {
        DEBUGMSGTL(("snmp_alarm", "no alarm %d to unregister\n", clientreg));
    }
}

/**
 * This function unregisters all alarms currently stored.
 *
 * @return void
 *
 * @see snmp_alarm_register
 * @see snmp_alarm_register_hr
 * @see snmp_alarm_unregister
 */
void
snmp_alarm_unregister_all(void)
{
  struct snmp_alarm *sa_ptr, *sa_tmp;

  for (sa_ptr = thealarms; sa_ptr != NULL; sa_ptr = sa_tmp) {
    sa_tmp = sa_ptr->next;
    free(sa_ptr);
  }
  DEBUGMSGTL(("snmp_alarm", "ALL alarms unregistered\n"));
  thealarms = NULL;
}  

struct snmp_alarm *
sa_find_next(void)
{
    struct snmp_alarm *a, *lowest = NULL;

    for (a = thealarms; a != NULL; a = a->next)
        if (!(a->flags & SA_FIRED)
            && (lowest == NULL || timercmp(&a->t_nextM, &lowest->t_nextM, <)))
            lowest = a;

    return lowest;
}

NETSNMP_IMPORT struct snmp_alarm *sa_find_specific(unsigned int clientreg);
struct snmp_alarm *
sa_find_specific(unsigned int clientreg)
{
    struct snmp_alarm *sa_ptr;
    for (sa_ptr = thealarms; sa_ptr != NULL; sa_ptr = sa_ptr->next) {
        if (sa_ptr->clientreg == clientreg) {
            return sa_ptr;
        }
    }
    return NULL;
}

void
run_alarms(void)
{
    struct snmp_alarm *a;
    unsigned int    clientreg;
    struct timeval  t_now;

    /*
     * Loop through everything we have repeatedly looking for the next thing to
     * call until all events are finally in the future again.  
     */

    while ((a = sa_find_next()) != NULL) {
        netsnmp_get_monotonic_clock(&t_now);

        if (timercmp(&a->t_nextM, &t_now, >))
            return;

        clientreg = a->clientreg;
        a->flags |= SA_FIRED;
        DEBUGMSGTL(("snmp_alarm", "run alarm %d\n", clientreg));
        (*(a->thecallback)) (clientreg, a->clientarg);
        DEBUGMSGTL(("snmp_alarm", "alarm %d completed\n", clientreg));

        a = sa_find_specific(clientreg);
        if (a) {
            a->t_lastM = t_now;
            timerclear(&a->t_nextM);
            a->flags &= ~SA_FIRED;
            sa_update_entry(a);
        } else {
            DEBUGMSGTL(("snmp_alarm", "alarm %d deleted itself\n",
                        clientreg));
        }
    }
}



RETSIGTYPE
alarm_handler(int a)
{
    run_alarms();
    set_an_alarm();
}



/**
 * Look up the time at which the next alarm will fire.
 *
 * @param[out] alarm_tm Time at which the next alarm will fire.
 * @param[in] now Earliest time that should be written into *alarm_tm.
 *
 * @return Zero if no alarms are scheduled; non-zero 'clientreg' value
 *   identifying the first alarm that will fire if one or more alarms are
 *   scheduled.
 */
int
netsnmp_get_next_alarm_time(struct timeval *alarm_tm, const struct timeval *now)
{
    struct snmp_alarm *sa_ptr;

    sa_ptr = sa_find_next();

    if (sa_ptr) {
        netsnmp_assert(alarm_tm);
        netsnmp_assert(timerisset(&sa_ptr->t_nextM));
        if (timercmp(&sa_ptr->t_nextM, now, >))
            *alarm_tm = sa_ptr->t_nextM;
        else
            *alarm_tm = *now;
        return sa_ptr->clientreg;
    } else {
        return 0;
    }
}

/**
 * Get the time until the next alarm will fire.
 *
 * @param[out] delta Time until the next alarm.
 *
 * @return Zero if no alarms are scheduled; non-zero 'clientreg' value
 *   identifying the first alarm that will fire if one or more alarms are
 *   scheduled.
 */
int
get_next_alarm_delay_time(struct timeval *delta)
{
    struct timeval t_now, alarm_tm;
    int res;

    netsnmp_get_monotonic_clock(&t_now);
    res = netsnmp_get_next_alarm_time(&alarm_tm, &t_now);
    if (res)
        NETSNMP_TIMERSUB(&alarm_tm, &t_now, delta);
    return res;
}


void
set_an_alarm(void)
{
    struct timeval  delta;
    int             nextalarm = get_next_alarm_delay_time(&delta);

    /*
     * We don't use signals if they asked us nicely not to.  It's expected
     * they'll check the next alarm time and do their own calling of
     * run_alarms().  
     */

    if (nextalarm && !netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID,
					NETSNMP_DS_LIB_ALARM_DONT_USE_SIG)) {
#if defined(HAVE_SETITIMER)
        struct itimerval it;

        it.it_value = delta;
        timerclear(&it.it_interval);

        signal(SIGALRM, alarm_handler);
        setitimer(ITIMER_REAL, &it, NULL);
        DEBUGMSGTL(("snmp_alarm", "schedule alarm %d in %ld.%03ld seconds\n",
                    nextalarm, (long) delta.tv_sec, (long)(delta.tv_usec / 1000)));
#elif defined(SIGALRM)
        signal(SIGALRM, alarm_handler);
        alarm(delta.tv_sec);
        DEBUGMSGTL(("snmp_alarm",
                    "schedule alarm %d in roughly %ld seconds\n", nextalarm,
                    delta.tv_sec));
#endif
    } else {
        DEBUGMSGTL(("snmp_alarm", "no alarms found to schedule\n"));
    }
}


/**
 * This function registers function callbacks to occur at a specific time
 * in the future.
 *
 * @param when is an unsigned integer specifying when the callback function
 *             will be called in seconds.
 *
 * @param flags is an unsigned integer that specifies how frequent the callback
 *	function is called in seconds.  Should be SA_REPEAT or 0.  If  
 *	flags  is  set with SA_REPEAT, then the registered callback function
 *	will be called every SA_REPEAT seconds.  If flags is 0 then the 
 *	function will only be called once and then removed from the 
 *	registered alarm list.
 *
 * @param thecallback is a pointer SNMPAlarmCallback which is the callback 
 *	function being stored and registered.
 *
 * @param clientarg is a void pointer used by the callback function.  This 
 *	pointer is assigned to snmp_alarm->clientarg and passed into the
 *	callback function for the client's specific needs.
 *
 * @return Returns a unique unsigned integer(which is also passed as the first 
 *	argument of each callback), which can then be used to remove the
 *	callback from the list at a later point in the future using the
 *	snmp_alarm_unregister() function.  If memory could not be allocated
 *	for the snmp_alarm struct 0 is returned.
 *
 * @see snmp_alarm_unregister
 * @see snmp_alarm_register_hr
 * @see snmp_alarm_unregister_all
 */
unsigned int
snmp_alarm_register(unsigned int when, unsigned int flags,
                    SNMPAlarmCallback * thecallback, void *clientarg)
{
    struct timeval  t;

    if (0 == when) {
        t.tv_sec = 0;
        t.tv_usec = 1;
    } else {
        t.tv_sec = when;
        t.tv_usec = 0;
    }

    return snmp_alarm_register_hr(t, flags, thecallback, clientarg);
}


/**
 * This function offers finer granularity as to when the callback 
 * function is called by making use of t->tv_usec value forming the 
 * "when" aspect of snmp_alarm_register().
 *
 * @param t is a timeval structure used to specify when the callback 
 *	function(alarm) will be called.  Adds the ability to specify
 *	microseconds.  t.tv_sec and t.tv_usec are assigned
 *	to snmp_alarm->tv_sec and snmp_alarm->tv_usec respectively internally.
 *	The snmp_alarm_register function only assigns seconds(it's when 
 *	argument).
 *
 * @param flags is an unsigned integer that specifies how frequent the callback
 *	function is called in seconds.  Should be SA_REPEAT or NULL.  If  
 *	flags  is  set with SA_REPEAT, then the registered callback function
 *	will be called every SA_REPEAT seconds.  If flags is NULL then the 
 *	function will only be called once and then removed from the 
 *	registered alarm list.
 *
 * @param cb is a pointer SNMPAlarmCallback which is the callback 
 *	function being stored and registered.
 *
 * @param cd is a void pointer used by the callback function.  This 
 *	pointer is assigned to snmp_alarm->clientarg and passed into the
 *	callback function for the client's specific needs.
 *
 * @return Returns a unique unsigned integer(which is also passed as the first 
 *	argument of each callback), which can then be used to remove the
 *	callback from the list at a later point in the future using the
 *	snmp_alarm_unregister() function.  If memory could not be allocated
 *	for the snmp_alarm struct 0 is returned.
 *
 * @see snmp_alarm_register
 * @see snmp_alarm_unregister
 * @see snmp_alarm_unregister_all
 */
unsigned int
snmp_alarm_register_hr(struct timeval t, unsigned int flags,
                       SNMPAlarmCallback * cb, void *cd)
{
    struct snmp_alarm **s = NULL;

    for (s = &(thealarms); *s != NULL; s = &((*s)->next));

    *s = SNMP_MALLOC_STRUCT(snmp_alarm);
    if (*s == NULL) {
        return 0;
    }

    (*s)->t = t;
    (*s)->flags = flags;
    (*s)->clientarg = cd;
    (*s)->thecallback = cb;
    (*s)->clientreg = regnum++;
    (*s)->next = NULL;

    sa_update_entry(*s);

    DEBUGMSGTL(("snmp_alarm",
                "registered alarm %d, t = %ld.%03ld, flags=0x%02x\n",
                (*s)->clientreg, (long) (*s)->t.tv_sec, (long)((*s)->t.tv_usec / 1000),
                (*s)->flags));

    if (start_alarms) {
        set_an_alarm();
    }

    return (*s)->clientreg;
}

/**
 * This function resets an existing alarm.
 *
 * @param clientreg is a unique unsigned integer representing a registered
 *	alarm which the client wants to unregister.
 *
 * @return 0 on success, -1 if the alarm was not found
 *
 * @see snmp_alarm_register
 * @see snmp_alarm_register_hr
 * @see snmp_alarm_unregister
 */
int
snmp_alarm_reset(unsigned int clientreg)
{
    struct snmp_alarm *a;
    struct timeval  t_now;
    if ((a = sa_find_specific(clientreg)) != NULL) {
        netsnmp_get_monotonic_clock(&t_now);
        a->t_lastM.tv_sec = t_now.tv_sec;
        a->t_lastM.tv_usec = t_now.tv_usec;
        a->t_nextM.tv_sec = 0;
        a->t_nextM.tv_usec = 0;
        NETSNMP_TIMERADD(&t_now, &a->t, &a->t_nextM);
        return 0;
    }
    DEBUGMSGTL(("snmp_alarm_reset", "alarm %d not found\n",
                clientreg));
    return -1;
}
/**  @} */
