OpenSolaris_b135/cmd/isns/isnsd/esi.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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);
}