root/usr/src/cmd/isns/isnsd/esi.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <time.h>
#include <signal.h>
#include <poll.h>

#include "isns_server.h"
#include "isns_cache.h"
#include "isns_obj.h"
#include "isns_pdu.h"
#include "isns_func.h"
#include "isns_qry.h"
#include "isns_msgq.h"
#include "isns_log.h"
#include "isns_sched.h"
#include "isns_scn.h"
#include "isns_esi.h"

/*
 * global variables.
 */

/*
 * local variables.
 */
static ev_t *ev_list = NULL;

static uint32_t stopwatch = 0;
static pthread_mutex_t stw_mtx = PTHREAD_MUTEX_INITIALIZER;

static int wakeup = 0;
static pthread_mutex_t idl_mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t idl_cond = PTHREAD_COND_INITIALIZER;

/*
 * external variables.
 */
extern const int UID_ATTR_INDEX[MAX_OBJ_TYPE_FOR_SIZE];

extern boolean_t time_to_exit;

extern msg_queue_t *sys_q;

extern uint64_t esi_threshold;

#ifdef DEBUG
extern void dump_pdu1(isns_pdu_t *);
#endif

/*
 * local functions.
 */
static void *esi_monitor(void *);

/*
 * ****************************************************************************
 *
 * new_esi_portal:
 *      Make a new portal for ESI event.
 *
 * uid  - the portal object UID.
 * ip6  - the portal IPv6 format IP address.
 * port - the portal port.
 * esip - the ESI port.
 * return - the new ESI portal.
 *
 * ****************************************************************************
 */
static esi_portal_t *
new_esi_portal(
        uint32_t uid,
        in6_addr_t *ip6,
        uint32_t port,
        uint32_t esip
)
{
        esi_portal_t *p;

        p = (esi_portal_t *)malloc(sizeof (esi_portal_t));
        if (p != NULL) {
                if (((int *)ip6)[0] == 0x00 &&
                    ((int *)ip6)[1] == 0x00 &&
                    ((uchar_t *)ip6)[8] == 0x00 &&
                    ((uchar_t *)ip6)[9] == 0x00 &&
                    ((uchar_t *)ip6)[10] == 0xFF &&
                    ((uchar_t *)ip6)[11] == 0xFF) {
                        p->sz = sizeof (in_addr_t);
                        p->ip4 = ((uint32_t *)ip6)[3];
                } else {
                        p->sz = sizeof (in6_addr_t);
                }
                p->ip6 = ip6;
                p->port = port;
                p->esip = esip;
                p->ref = uid;
                p->so = 0;
                p->next = NULL;
        }

        return (p);
}

/*
 * ****************************************************************************
 *
 * free_esi_portal:
 *      Free a list of portal of one ESI event.
 *
 * p    - the ESI portal.
 *
 * ****************************************************************************
 */
static void
free_esi_portal(
        esi_portal_t *p
)
{
        esi_portal_t *n;

        while (p != NULL) {
                n = p->next;
                free(p->ip6);
                free(p);
                p = n;
        }
}

/*
 * ****************************************************************************
 *
 * ev_new:
 *      Make a new ESI event.
 *
 * uid  - the Entity object UID.
 * eid  - the Entity object name.
 * len  - the length of the name.
 * return - the ESI event.
 *
 * ****************************************************************************
 */
static ev_t *
ev_new(
        uint32_t uid,
        uchar_t *eid,
        uint32_t len
)
{
        ev_t *ev;

        ev = (ev_t *)malloc(sizeof (ev_t));
        if (ev != NULL) {
                if (pthread_mutex_init(&ev->mtx, NULL) != 0 ||
                    (ev->eid = (uchar_t *)malloc(len)) == NULL) {
                        free(ev);
                        return (NULL);
                }
                ev->uid = uid;
                (void) strcpy((char *)ev->eid, (char *)eid);
                ev->eid_len = len;
                /* initialization time */
                ev->flags = EV_FLAG_INIT;
        }

        return (ev);
}

/*
 * ****************************************************************************
 *
 * cb_portal_uids:
 *      Callback function which makes a copy of the portal child object
 *      UIDs from a Network Entity object.
 *
 * p1   - the Network Entity object.
 * p2   - the lookup control data.
 * return - the number of portal object UIDs.
 *
 * ****************************************************************************
 */
static int
cb_portal_uids(
        void *p1,
        void *p2
)
{
        isns_obj_t *obj = (isns_obj_t *)p1;
        lookup_ctrl_t *lcp = (lookup_ctrl_t *)p2;

        isns_attr_t *attr;

        uint32_t *cuidp;

        uint32_t num = 0;
        uint32_t *p = NULL;

        cuidp = get_child_t(obj, OBJ_PORTAL);
        if (cuidp != NULL) {
                p = (uint32_t *)malloc(*cuidp * sizeof (*p));
                if (p != NULL) {
                        num = *cuidp ++;
                        (void) memcpy(p, cuidp, num * sizeof (*p));
                        lcp->data[1].ptr = (uchar_t *)p;
                }
        }

        attr = &obj->attrs[ATTR_INDEX_ENTITY(ISNS_ENTITY_REG_PERIOD_ATTR_ID)];
        if (attr->tag != 0 && attr->value.ui != 0) {
                lcp->data[2].ui = attr->value.ui;
        } else {
                /* just one second before the end of the world */
                lcp->data[2].ui = INFINITY - 1;
        }

        return (num);
}

/*
 * ****************************************************************************
 *
 * cb_esi_portal:
 *      Callback function which gets ESI port number and ESI interval
 *      from a portal object.
 *
 * p1   - the Portal object.
 * p2   - the lookup control data.
 * return - the ESI interval.
 *
 * ****************************************************************************
 */
static int
cb_esi_portal(
        void *p1,
        void *p2
)
{
        uint32_t intval = 0;

        isns_obj_t *obj;
        lookup_ctrl_t *lcp;

        in6_addr_t *ip;
        uint32_t esip;

        isns_attr_t *attr;

        if (cb_clone_attrs(p1, p2) == 0) {
                obj = (isns_obj_t *)p1;
                lcp = (lookup_ctrl_t *)p2;
                ip = lcp->data[1].ip;
                esip = lcp->data[2].ui;
                if (esip != 0) {
                        attr = &obj->attrs[ATTR_INDEX_PORTAL(
                            ISNS_PORTAL_PORT_ATTR_ID)];
                        lcp->data[0].ui = attr->value.ui;
                        attr = &obj->attrs[ATTR_INDEX_PORTAL(
                            ISNS_ESI_INTERVAL_ATTR_ID)];
                        if (attr->tag != 0 && attr->value.ui != 0) {
                                intval = attr->value.ui;
                        } else {
                                intval = DEFAULT_ESI_INTVAL;
                        }
                } else {
                        free(ip);
                }
        }

        return ((int)intval);
}

/*
 * ****************************************************************************
 *
 * extract_esi_portal:
 *      Extract a list of portal which have an ESI port for an Entity.
 *
 * uid  - the Entity object UID.
 * intval - the ESI interval for returnning.
 * return - the list of portals.
 *
 * ****************************************************************************
 */
static esi_portal_t *
extract_esi_portal(
        uint32_t uid,
        uint32_t *intval
)
{
        esi_portal_t *list = NULL;
        esi_portal_t *p;

        lookup_ctrl_t lc;

        uint32_t num_of_portal;
        uint32_t *portal_uids;

        uint32_t intv;

        /* prepare for looking up entity object */
        SET_UID_LCP(&lc, OBJ_ENTITY, uid);
        lc.data[1].ptr = NULL;
        lc.data[2].ui = INFINITY - 1;

        /* get the array of the portal uid(s) */
        num_of_portal = (uint32_t)cache_lookup(&lc, NULL, cb_portal_uids);
        portal_uids = (uint32_t *)lc.data[1].ptr;
        *intval = lc.data[2].ui;

        /* prepare for looking up portal object(s) */
        SET_UID_LCP(&lc, OBJ_PORTAL, 0);
        lc.id[1] = ISNS_PORTAL_IP_ADDR_ATTR_ID;
        lc.id[2] = ISNS_ESI_PORT_ATTR_ID;
        FOR_EACH_OBJS(portal_uids, num_of_portal, uid, {
                if (uid != 0) {
                        lc.data[0].ui = uid;
                        intv = cache_lookup(&lc, NULL, cb_esi_portal);
                        if (intv != 0) {
                                p = new_esi_portal(uid,
                                    (in6_addr_t *)lc.data[1].ip,
                                    lc.data[0].ui, lc.data[2].ui);
                                if (p != NULL) {
                                        p->next = list;
                                        list = p;
                                        if (*intval > intv) {
                                                *intval = intv;
                                        }
                                }
                        }
                }
        });

        /* free up the portal uid array */
        free(portal_uids);

        return (list);
}

/*
 * ****************************************************************************
 *
 * ev_add:
 *      Add an ESI event.
 *
 * ev   - the ESI event.
 * init - 0: initialization time, otherwise not.
 * return - error code.
 *
 * ****************************************************************************
 */
static int
ev_add(
        ev_t *ev,
        int init
)
{
        uint32_t intval;
        esi_portal_t *p;

        double rnd;
        uint32_t t = 0;

        /* get the portal(s) which are registered for ESI monitoring */
        /* and the second interval for ESI or registration expiration */
        p = extract_esi_portal(ev->uid, &intval);
        ev->intval = intval;
        if (p != NULL) {
                ev->type = EV_ESI;
                ev->portal = p;
                /* avoid running everything at the same time */
                if (init != 0) {
                        /* generate random number within range (0, 1] */
                        rnd = (rand() + 1) / (double)(RAND_MAX + 1);
                        t = (uint32_t)(intval * rnd);
                }
        } else {
                /* no portal is registered for ESI monitoring, make */
                /* an entry for entity registration expiration */
                ev->type = EV_REG_EXP;
                ev->portal = NULL;
                if (init != 0) {
                        t = intval;
                }
        }

        /* schedule the event */
        return (el_add(ev, t, NULL));
}

/*
 * global functions.
 */

/*
 * ****************************************************************************
 *
 * sigalrm:
 *      The signal handler for SIGALRM, the ESI proc uses the SIGALRM
 *      for waking up to perform the client status inquery.
 *
 * sig  - the signal.
 *
 * ****************************************************************************
 */
/*ARGSUSED*/
void
sigalrm(
        int sig
)
{
        /* wake up the idle */
        (void) pthread_mutex_lock(&idl_mtx);
        wakeup = 1; /* wake up naturally */
        (void) pthread_cond_signal(&idl_cond);
        (void) pthread_mutex_unlock(&idl_mtx);
}

/*
 * ****************************************************************************
 *
 * esi_load:
 *      Load an ESI event from data store.
 *
 * uid  - the Entity object UID.
 * eid  - the Entity object name.
 * len  - the length of the name.
 * return - error code.
 *
 * ****************************************************************************
 */
int
esi_load(
        uint32_t uid,
        uchar_t *eid,
        uint32_t len
)
{
        int ec = 0;

        /* make a new event */
        ev_t *ev = ev_new(uid, eid, len);

        /* put the new event to the list */
        if (ev != NULL) {
                ev->next = ev_list;
                ev_list = ev;
        } else {
                ec = ISNS_RSP_INTERNAL_ERROR;
        }

        return (ec);
}

/*
 * ****************************************************************************
 *
 * verify_esi_portal:
 *      Verify ESI port and add the ESI entries after the ESI are loaded.
 *
 * return - error code.
 *
 * ****************************************************************************
 */
int
verify_esi_portal(
)
{
        int ec = 0;

        ev_t *ev;

        /* add each event from the list */
        while (ev_list != NULL && ec == 0) {
                ev = ev_list;
                ev_list = ev->next;
                ev->next = NULL;
                ec = ev_add(ev, 1);
        }

        return (ec);
}

/*
 * ****************************************************************************
 *
 * esi_add:
 *      Add a new ESI event when a new Entity is registered.
 *
 * uid  - the Entity object UID.
 * eid  - the Entity object name.
 * len  - the length of the name.
 * return - error code.
 *
 * ****************************************************************************
 */
int
esi_add(
        uint32_t uid,
        uchar_t *eid,
        uint32_t len
)
{
        int ec = 0;

        /* make a new event */
        ev_t *ev = ev_new(uid, eid, len);

        if (ev != NULL) {
                /* interrupt idle */
                ev->flags |= EV_FLAG_WAKEUP;
                ec = ev_add(ev, 0);
        } else {
                ec = ISNS_RSP_INTERNAL_ERROR;
        }

        return (ec);
}

/*
 * ****************************************************************************
 *
 * esi_remove:
 *      Remove an ESI event immediately.
 *
 * uid  - the Entity object UID.
 * return - always successful.
 *
 * ****************************************************************************
 */
int
esi_remove(
        uint32_t uid
)
{
        (void) el_remove(uid, 0, 0);

        return (0);
}

/*
 * ****************************************************************************
 *
 * esi_remove_obj:
 *      Update an ESI event when a Entity object or a Portal object is
 *      removed from server. If the object is being removed because of
 *      ESI failure, the ESI event will be removed with a pending time,
 *      otherwise, the ESI will be removed immediately.
 *
 * obj  - the object being removed.
 * pending - the pending flag.
 * return - always successful.
 *
 * ****************************************************************************
 */
int
esi_remove_obj(
        const isns_obj_t *obj,
        int pending
)
{
        uint32_t puid, uid;

        switch (obj->type) {
        case OBJ_PORTAL:
                puid = get_parent_uid(obj);
                uid = get_obj_uid(obj);
                break;
        case OBJ_ENTITY:
                puid = get_obj_uid(obj);
                uid = 0;
                break;
        default:
                puid = 0;
                break;
        }

        if (puid != 0) {
                (void) el_remove(puid, uid, pending);
        }

        return (0);
}

/*
 * ****************************************************************************
 *
 * get_stopwatch:
 *      Get the stopwatch. It might need to signal the condition to
 *      wake up the idle so the stopwatch gets updated.
 *
 * flag - wake up flag.
 * return - the stopwatch.
 *
 * ****************************************************************************
 */
uint32_t
get_stopwatch(
        int flag
)
{
        uint32_t t;

        /* not re-schedule, wake up idle */
        (void) pthread_mutex_lock(&idl_mtx);
        if (flag != 0) {
                wakeup = 2; /* wake up manually */
                (void) pthread_cond_signal(&idl_cond);
        } else {
                wakeup = 0; /* clear previous interruption */
        }
        (void) pthread_mutex_unlock(&idl_mtx);

        /* get most current time */
        (void) pthread_mutex_lock(&stw_mtx);
        t = stopwatch;
        (void) pthread_mutex_unlock(&stw_mtx);

        return (t);
}

/*
 * ****************************************************************************
 *
 * ev_intval:
 *      Get the time interval of an ESI event.
 *
 * p    - the ESI event.
 * return - the time interval.
 *
 * ****************************************************************************
 */
uint32_t
ev_intval(
        void *p
)
{
        return (((ev_t *)p)->intval);
}

/*
 * ****************************************************************************
 *
 * ev_match:
 *      Check the ESI event maching an Entity object.
 *
 * p    - the ESI event.
 * uid  - the Entity object UID.
 * return - 1: match, otherwise not.
 *
 * ****************************************************************************
 */
int
ev_match(
        void *p,
        uint32_t uid
)
{
        if (((ev_t *)p)->uid == uid) {
                return (1);
        } else {
                return (0);
        }
}

/*
 * ****************************************************************************
 *
 * ev_remove:
 *      Remove a portal or an ESI event. If all of ESI portal has been
 *      removed, the ESI event will be marked as removal pending, which
 *      will result in removing the Entity object after the pending time.
 *
 * p    - the ESI event.
 * portal_uid   - the Portal object UID.
 * flag - 0: the ESI is currently in use, otherwise it is scheduled.
 * pending      - flag for the ESI removal pending.
 * return - 0: the ESI is physically removed, otherwise not.
 *
 * ****************************************************************************
 */
int
ev_remove(
        void *p,
        uint32_t portal_uid,
        int flag,
        int pending
)
{
        ev_t *ev = (ev_t *)p;
        esi_portal_t **pp, *portal;

        int has_portal = 0;
        int state;

        /* remove one portal only */
        if (portal_uid != 0) {
                pp = &ev->portal;
                portal = *pp;
                while (portal != NULL) {
                        /* found the match portal */
                        if (portal->ref == portal_uid) {
                                /* mark it as removed */
                                portal->ref = 0;
                                if (flag != 0) {
                                        /* not in use, remove it physically */
                                        *pp = portal->next;
                                        portal->next = NULL;
                                        free_esi_portal(portal);
                                } else {
                                        pp = &portal->next;
                                }
                        } else {
                                /* one or more esi portals are available */
                                if (portal->ref != 0) {
                                        has_portal = 1;
                                }
                                pp = &portal->next;
                        }
                        portal = *pp;
                }
        }

        /* no portal available */
        if (has_portal == 0) {
                state = (pending << 1) | flag;
                switch (state) {
                case 0x0:
                        /* mark the event as removed */
                        ev->flags |= EV_FLAG_REMOVE;
                        isnslog(LOG_DEBUG, "ev_remove",
                            "%s [%d] is marked as removed.",
                            ev->type == EV_ESI ? "ESI" : "REG_EXP",
                            ev->uid);
                        break;
                case 0x1:
                        /* physically remove the event */
                        ev_free(ev);
                        break;
                case 0x2:
                case 0x3:
                        /* mark the event as removal pending */
                        isnslog(LOG_DEBUG, "ev_remove",
                            "%s [%d] is marked as removal pending.",
                            ev->type == EV_ESI ? "ESI" : "REG_EXP",
                            ev->uid);
                        ev->flags |= EV_FLAG_REM_P1;
                        has_portal = 1;
                        break;
                default:
                        break;
                }
        } else {
                isnslog(LOG_DEBUG, "ev_remove", "%s [%d] removed portal %d.",
                    ev->type == EV_ESI ? "ESI" : "REG_EXP",
                    ev->uid, portal_uid);
        }

        return (has_portal);
}

/*
 * ****************************************************************************
 *
 * ev_free:
 *      Free an ESI event.
 *
 * p    - the ESI event.
 *
 * ****************************************************************************
 */
void
ev_free(
        void *p
)
{
        ev_t *ev = (ev_t *)p;

        /* free up all of portals */
        free_esi_portal(ev->portal);

        isnslog(LOG_DEBUG, "ev_free",
            "%s [%d] is physically removed.",
            ev->type == EV_ESI ? "ESI" : "REG_EXP",
            ev->uid);

        free(ev->eid);

        /* free the event */
        free(ev);
}

/*
 * ****************************************************************************
 *
 * evf_init:
 *      Check the initial flag of an ESI event.
 *
 * p    - the ESI event.
 * return - 0: not initial, otherwise yes.
 *
 * ****************************************************************************
 */
int
evf_init(
        void *p
)
{
        return (((ev_t *)p)->flags & EV_FLAG_INIT);
}

/*
 * ****************************************************************************
 *
 * evf_again:
 *      Check the again flag of an ESI event.
 *      (this flag might be eliminated and use the init flag.)
 *
 * p    - the ESI event.
 * return - 0: not again, otherwise yes.
 *
 * ****************************************************************************
 */
int
evf_again(
        void *p
)
{
        return (((ev_t *)p)->flags & EV_FLAG_AGAIN);
}

/*
 * ****************************************************************************
 *
 * evf_wakeup:
 *      Check the wakeup flag of an ESI event. The idle might need to
 *      wake up before the event is scheduled.
 *
 * p    - the ESI event.
 * return - 0: no wakeup, otherwise yes.
 *
 * ****************************************************************************
 */
int
evf_wakeup(
        void *p
)
{
        return (((ev_t *)p)->flags & EV_FLAG_WAKEUP);
}

/*
 * ****************************************************************************
 *
 * evf_rem:
 *      Check the removal flag of an ESI event. The ESI entry might be
 *      marked as removal.
 *
 * p    - the ESI event.
 * return - 0: not removed, otherwise yes.
 *
 * ****************************************************************************
 */
int
evf_rem(
        void *p
)
{
        return (((ev_t *)p)->flags & EV_FLAG_REMOVE);
}

/*
 * ****************************************************************************
 *
 * evf_rem_pending:
 *      Check the removal pending flag of an ESI event. The ESI entry
 *      might be marked as removal pending. If it is, we will switch the
 *      event type and change the time interval.
 *
 * p    - the ESI event.
 * return - 0: not removal pending, otherwise yes.
 *
 * ****************************************************************************
 */
int
evf_rem_pending(
        void *p
)
{
        ev_t *ev = (ev_t *)p;
        if ((ev->flags & EV_FLAG_REM_P) != 0) {
                if (ev->type != EV_REG_EXP) {
                        isnslog(LOG_DEBUG, "ev_rem_pending",
                            "%s [%d] is changed to REG_EXP.",
                            ev->type == EV_ESI ? "ESI" : "REG_EXP",
                            ev->uid);
                        ev->type = EV_REG_EXP;
                        ev->intval *= 2; /* after 2 ESI interval */
                }
                return (1);
        }

        return (0);
}

/*
 * ****************************************************************************
 *
 * evf_zero:
 *      Reset the event flag.
 *
 * p    - the ESI event.
 *
 * ****************************************************************************
 */
void
evf_zero(
        void *p
)
{
        ev_t *ev = (ev_t *)p;

        /* not acutally clear it, need to set again flag */
        /* and keep the removal pending flag */
        ev->flags = EV_FLAG_AGAIN | (ev->flags & EV_FLAG_REM_P);
}

/*
 * ****************************************************************************
 *
 * evl_append:
 *      Append an ESI event to the list, the list contains all of
 *      ESI events which are being processed at present.
 *
 * p    - the ESI event.
 *
 * ****************************************************************************
 */
void
evl_append(
        void *p
)
{
        ev_t *ev;

        ev = (ev_t *)p;
        ev->next = ev_list;
        ev_list = ev;
}

/*
 * ****************************************************************************
 *
 * evl_strip:
 *      Strip off an ESI event from the list after the event is being
 *      processed, it will be scheduled in the scheduler.
 *
 * p    - the ESI event.
 *
 * ****************************************************************************
 */
void
evl_strip(
        void *p
)
{
        ev_t **evp = &ev_list;
        ev_t *ev = *evp;

        while (ev != NULL) {
                if (ev == p) {
                        *evp = ev->next;
                        break;
                }
                evp = &ev->next;
                ev = *evp;
        }
}

/*
 * ****************************************************************************
 *
 * evl_remove:
 *      Remove an ESI event or a portal of an ESI event from the event list.
 *
 * id1  - the Entity object UID.
 * id2  - the Portal object UID.
 * pending - the pending flag.
 * return - 1: found a match event, otherwise not.
 *
 * ****************************************************************************
 */
int
evl_remove(
        uint32_t id1,
        uint32_t id2,
        int pending
)
{
        ev_t *ev = ev_list;

        while (ev != NULL) {
                /* found it */
                if (ev_match(ev, id1) != 0) {
                        /* lock the event */
                        (void) pthread_mutex_lock(&ev->mtx);
                        /* mark it as removed */
                        (void) ev_remove(ev, id2, 0, pending);
                        /* unlock the event */
                        (void) pthread_mutex_unlock(&ev->mtx);
                        /* tell caller removal is done */
                        return (1);
                }
                ev = ev->next;
        }

        /* not found it */
        return (0);
}

#define ALARM_MAX       (21427200)

/*
 * ****************************************************************************
 *
 * idle:
 *      Idle for certain amount of time or a wakeup signal is recieved.
 *
 * t    - the idle time.
 * return - the time that idle left.
 *
 * ****************************************************************************
 */
static int
idle(
        uint32_t t
)
{
        uint32_t t1, t2, t3 = 0;
        int idl_int = 0;

        /* hold the mutex for stopwatch update */
        (void) pthread_mutex_lock(&stw_mtx);

        do {
                if (t > ALARM_MAX) {
                        t1 = ALARM_MAX;
                } else {
                        t1 = t;
                }

                /* start alarm */
                (void) alarm(t1);

                /* hold the mutex for idle condition */
                (void) pthread_mutex_lock(&idl_mtx);

                /* wait on condition variable to wake up idle */
                while (wakeup == 0) {
                        (void) pthread_cond_wait(&idl_cond, &idl_mtx);
                }
                if (wakeup == 2) {
                        idl_int = 1;
                }
                /* clean wakeup flag */
                wakeup = 0;

                /* release the mutex for idle condition */
                (void) pthread_mutex_unlock(&idl_mtx);

                /* stop alarm */
                t2 = alarm(0);

                /* seconds actually slept */
                t3 += t1 - t2;
                t -= t3;
        } while (t > 0 && idl_int == 0);

        /* increate the stopwatch by the actually slept time */
        stopwatch += t3;

        /* release the mutex after stopwatch is updated */
        (void) pthread_mutex_unlock(&stw_mtx);

        /* return the amount of time which is not slept */
        return (t);
}

/*
 * ****************************************************************************
 *
 * ev_ex:
 *      Execute an event. To inquiry the client status or
 *      perform registration expiration.
 *
 * ev   - the event.
 *
 * ****************************************************************************
 */
static void
ev_ex(
        ev_t *ev
)
{
        pthread_t tid;

        switch (ev->type) {
        case EV_ESI:
                if (pthread_create(&tid, NULL,
                    esi_monitor, (void *)ev) != 0) {
                        isnslog(LOG_DEBUG, "ev_ex", "pthread_create() failed.");
                        /* reschedule for next occurence */
                        (void) el_add(ev, 0, NULL);
                } else {
                        /* increase the thread ref count */
                        inc_thr_count();
                }
                break;
        case EV_REG_EXP:
                (void) queue_msg_set(sys_q, REG_EXP, (void *)ev);
                break;
        default:
                break;
        }
}

/*
 * ****************************************************************************
 *
 * esi_proc:
 *      ESI thread entry, which:
 *      1: fetch an event from schedule,
 *      2: idle for some time,
 *      3: execute the event or re-schedule it,
 *      4: repeat from step 1 before server is being shutdown.
 *
 * arg  - the thread argument.
 *
 * ****************************************************************************
 */
/*ARGSUSED*/
void *
esi_proc(
        void *arg
)
{
        uint32_t t, t1, pt;
        ev_t *ev;

        void *evp;

        while (time_to_exit == B_FALSE) {
                ev = (ev_t *)el_first(&pt);

                /* caculate the idle time */
                if (ev != NULL) {
                        if (pt > stopwatch) {
                                t = pt - stopwatch;
                        } else {
                                t = 0;
                        }
                } else {
                        t = INFINITY;
                }

                do {
                        /* block for a certain amount of time */
                        if (t > 0) {
                                isnslog(LOG_DEBUG, "esi_proc",
                                    "idle for %d seconds.", t);
                                t1 = idle(t);
                        } else {
                                t1 = 0;
                        }
                        if (t1 > 0) {
                                isnslog(LOG_DEBUG, "esi_proc",
                                    "idle interrupted after idle for "
                                    "%d seconds.", t - t1);
                        }
                        if (time_to_exit != B_FALSE) {
                                ev = NULL; /* force break */
                        } else if (ev != NULL) {
                                if (t1 > 0) {
                                        /* not naturally waken up */
                                        /* reschedule current event */
                                        evp = NULL;
                                        (void) el_add(ev, pt, &evp);
                                        ev = (ev_t *)evp;
                                        t = t1;
                                } else {
                                        /* excute */
                                        isnslog(LOG_DEBUG, "esi_proc",
                                            "excute the cron job[%d].",
                                            ev->uid);
                                        ev_ex(ev);
                                        ev = NULL;
                                }
                        }
                } while (ev != NULL);
        }

        return (NULL);
}

/*
 * ****************************************************************************
 *
 * esi_ping:
 *      Ping the client with the ESI retry threshold for status inquiry.
 *
 * so   - the socket descriptor.
 * pdu  - the ESI packet.
 * pl   - the length of packet.
 * return - 1: status inquired, otherwise not.
 *
 * ****************************************************************************
 */
static int
esi_ping(
        int so,
        isns_pdu_t *pdu,
        size_t pl
)
{
        int try_cnt = 0;
        isns_pdu_t *rsp = NULL;
        size_t rsp_sz;

        int alive = 0;

        do {
                if (isns_send_pdu(so, pdu, pl) == 0) {
                        if (isns_rcv_pdu(so, &rsp, &rsp_sz,
                            ISNS_RCV_SHORT_TIMEOUT) > 0) {
#ifdef DEBUG
                                dump_pdu1(rsp);
#endif
                                alive = 1;
                                break;
                        }
                } else {
                        /* retry after 1 second */
                        (void) sleep(1);
                }
                try_cnt ++;
        } while (try_cnt < esi_threshold);

        if (rsp != NULL) {
                free(rsp);
        }

        return (alive);
}

/*
 * ****************************************************************************
 *
 * esi_monitor:
 *      Child thread for client status mornitoring.
 *
 * arg  - the ESI event.
 *
 * ****************************************************************************
 */
static void *
esi_monitor(
        void *arg
)
{
        ev_t *ev = (ev_t *)arg;

        esi_portal_t *p;
        int so;

        isns_pdu_t *pdu = NULL;
        size_t sz;
        size_t pl;
        size_t half;

        time_t t;

        int feedback;

        /* lock the event for esi monitoring */
        (void) pthread_mutex_lock(&ev->mtx);

        if (evf_rem(ev) != 0) {
                goto mon_done;
        } else if (evf_rem_pending(ev) != 0) {
                goto mon_done;
        }

        /* timestamp */
        t = time(NULL);

        /* allocate ESI PDU */
        if (pdu_reset_esi(&pdu, &pl, &sz) != 0 ||
            pdu_add_tlv(&pdu, &pl, &sz,
            ISNS_TIMESTAMP_ATTR_ID, 8, (void *)&t, 1) != 0 ||
            pdu_add_tlv(&pdu, &pl, &sz,
            ISNS_EID_ATTR_ID, ev->eid_len, (void *)ev->eid, 0) != 0) {
                /* no memory, will retry later */
                goto mon_done;
        }

        /* set pdu head */
        pdu->version = htons((uint16_t)ISNSP_VERSION);
        pdu->func_id = htons((uint16_t)ISNS_ESI);
        pdu->xid = htons(get_server_xid());

        /* keep the current lenght of the playload */
        half = pl;

        p = ev->portal;
        while (p != NULL) {
                if (p->ref != 0 &&
                    /* skip IPv6 portal */
                    p->sz != sizeof (in6_addr_t) &&
                    pdu_add_tlv(&pdu, &pl, &sz,
                    ISNS_PORTAL_IP_ADDR_ATTR_ID,
                    sizeof (in6_addr_t), (void *)p->ip6, 0) == 0 &&
                    pdu_add_tlv(&pdu, &pl, &sz,
                    ISNS_PORTAL_PORT_ATTR_ID,
                    4, (void *)p->port, 0) == 0) {
                        /* connect once */
                        so = connect_to(p->sz, p->ip4, p->ip6, p->esip);
                        if (so != -1) {
                                feedback = esi_ping(so, pdu, pl);
                                (void) close(so);
                                /* p->so = so; */
                        } else {
                                /* cannot connect, portal is dead */
                                feedback = 0;
                        }
                        if (feedback == 0) {
                                isnslog(LOG_DEBUG, "esi_monitor",
                                    "ESI ping failed.");
                                (void) queue_msg_set(sys_q, DEAD_PORTAL,
                                    (void *)p->ref);
                        } else {
                                goto mon_done;
                        }
                }
                pl = half;
                p = p->next;
        }

mon_done:
        /* unlock the event after esi monitoring is done */
        (void) pthread_mutex_unlock(&ev->mtx);

        /* clean up pdu */
        if (pdu != NULL) {
                free(pdu);
        }

        /* set reschedule flags */
        ev->flags |= EV_FLAG_WAKEUP;

        /* reschedule for next occurence */
        (void) el_add(ev, 0, NULL);

        /* decrease the thread ref count */
        dec_thr_count();

        return (NULL);
}

/*
 * ****************************************************************************
 *
 * portal_dies:
 *      Handles the dead portal that ESI detected.
 *
 * uid  - the Portal object UID.
 *
 * ****************************************************************************
 */
void
portal_dies(
        uint32_t uid
)
{
        int ec = 0;

        lookup_ctrl_t lc;

        /* prepare the lookup control for deregistration */
        SET_UID_LCP(&lc, OBJ_PORTAL, uid);

        /* lock the cache for object deregistration */
        (void) cache_lock_write();

        /* deregister the portal */
        ec = dereg_object(&lc, 1);

        /* unlock cache and sync with data store */
        (void) cache_unlock_sync(ec);
}

/*
 * ****************************************************************************
 *
 * portal_dies:
 *      Handles the Entity registration expiration.
 *
 * p    - the ESI event.
 *
 * ****************************************************************************
 */
void
reg_expiring(
        void *p
)
{
        int ec = 0;
        ev_t *ev = (ev_t *)p;
        lookup_ctrl_t lc;

        /* prepare the lookup control for deregistration */
        SET_UID_LCP(&lc, OBJ_ENTITY, ev->uid);

        /* lock the cache for object deregistration */
        (void) cache_lock_write();

        if (evf_rem(ev) == 0) {
                /* deregister the entity */
                ec = dereg_object(&lc, 0);

                /* unlock cache and sync with data store */
                ec = cache_unlock_sync(ec);

                if (ec == 0) {
                        /* successfuk, mark ev as removed */
                        ev->flags |= EV_FLAG_REMOVE;
                } else {
                        /* failed, retry after 3 mintues */
                        ev->intval = 3 * 60;
                        isnslog(LOG_DEBUG, "reg_expiring",
                            "dereg failed, retry after 3 mintues.");
                }
        } else {
                /* ev is marked as removed, no need to dereg */
                (void) cache_unlock_nosync();
        }

        /* reschedule it for next occurence */
        (void) el_add(ev, 0, NULL);
}