static void
vca_kev(const struct kevent *kp)
{
    int i, j;
    struct sess *sp;
    struct sess *ss[NKEV];

    AN(kp->udata);
    if (kp->udata == vca_pipes) {
        j = 0;
        i = read(vca_pipes[0], ss, sizeof ss);
        if (i == -1 && errno == EAGAIN)
            return;
        while (i >= sizeof ss[0]) {
            CHECK_OBJ_NOTNULL(ss[j], SESS_MAGIC);
            assert(ss[j]->sp_fd >= 0);
            AZ(ss[j]->obj);
            VTAILQ_INSERT_TAIL(&sesshead, ss[j], list);
            vca_kq_sess(ss[j], EV_ADD | EV_ONESHOT);
            j++;
            i -= sizeof ss[0];
        }
        assert(i == 0);
        return;
    }
    CAST_OBJ_NOTNULL(sp, kp->udata, SESS_MAGIC);
    DSL(0x04, SLT_Debug, sp->id, "KQ: sp %p kev data %lu flags 0x%x%s",
        sp, (unsigned long)kp->data, kp->flags,
        (kp->flags & EV_EOF) ? " EOF" : "");

    assert(sp->id == kp->ident);
    assert(sp->sp_fd == sp->id);
    if (kp->data > 0) {
        i = HTC_Rx(sp->htc);
        if (i == 0) {
            vca_kq_sess(sp, EV_ADD | EV_ONESHOT);
            return;	/* more needed */
        }
        VTAILQ_REMOVE(&sesshead, sp, list);
        vca_handover(sp, i);
        return;
    } else if (kp->flags & EV_EOF) {
        VTAILQ_REMOVE(&sesshead, sp, list);
        vca_close_session(sp, "EOF");
        SES_Delete(sp);
        return;
    } else {
        VSL(SLT_Debug, sp->id, "KQ: sp %p kev data %lu flags 0x%x%s",
            sp, (unsigned long)kp->data, kp->flags,
            (kp->flags & EV_EOF) ? " EOF" : "");
    }
}
static inline void
vca_port_ev(port_event_t *ev) {
	struct sess *sp;
	if(ev->portev_source == PORT_SOURCE_USER) {
		CAST_OBJ_NOTNULL(sp, ev->portev_user, SESS_MAGIC);
		assert(sp->fd >= 0);
		AZ(sp->obj);
		VTAILQ_INSERT_TAIL(&sesshead, sp, list);
		vca_add(sp->fd, sp);
	} else {
		int i;
		assert(ev->portev_source == PORT_SOURCE_FD);
		CAST_OBJ_NOTNULL(sp, ev->portev_user, SESS_MAGIC);
		assert(sp->fd >= 0);
		if(ev->portev_events & POLLERR) {
			vca_del(sp->fd);
			VTAILQ_REMOVE(&sesshead, sp, list);			
			vca_close_session(sp, "EOF");
			SES_Delete(sp);
			return;
		}
		i = HTC_Rx(sp->htc);

		if (i == 0) {
			/* incomplete header, wait for more data */
			vca_add(sp->fd, sp);
			return;
		}

		/* 
		 * note: the original man page for port_associate(3C) states:
		 *
		 *    When an event for a PORT_SOURCE_FD object is retrieved,
		 *    the object no longer has an association with the port.
		 *
		 * This can be read along the lines of sparing the
		 * port_dissociate after port_getn(), but in fact,
		 * port_dissociate should be used
		 *
		 * Ref: http://opensolaris.org/jive/thread.jspa?threadID=129476&tstart=0
		 */
		vca_del(sp->fd);
		VTAILQ_REMOVE(&sesshead, sp, list);

		/* vca_handover will also handle errors */
		vca_handover(sp, i);
	}
	return;
}
static void
vca_eev(const struct epoll_event *ep)
{
	struct sess *ss[NEEV], *sp;
	int i, j;

	AN(ep->data.ptr);
	if (ep->data.ptr == vca_pipes) {
		if (ep->events & EPOLLIN || ep->events & EPOLLPRI) {
			j = 0;
			i = read(vca_pipes[0], ss, sizeof ss);
			if (i == -1 && errno == EAGAIN)
				return;
			while (i >= sizeof ss[0]) {
				CHECK_OBJ_NOTNULL(ss[j], SESS_MAGIC);
				assert(ss[j]->fd >= 0);
				AZ(ss[j]->obj);
				VTAILQ_INSERT_TAIL(&sesshead, ss[j], list);
				vca_cond_modadd(ss[j]->fd, ss[j]);
				j++;
				i -= sizeof ss[0];
			}
			assert(i == 0);
		}
	} else {
		CAST_OBJ_NOTNULL(sp, ep->data.ptr, SESS_MAGIC);
		if (ep->events & EPOLLIN || ep->events & EPOLLPRI) {
			i = HTC_Rx(sp->htc);
			if (i == 0) {
				vca_modadd(sp->fd, sp, EPOLL_CTL_MOD);
				return;	/* more needed */
			}
			VTAILQ_REMOVE(&sesshead, sp, list);
			vca_handover(sp, i);
		} else if (ep->events & EPOLLERR) {
			VTAILQ_REMOVE(&sesshead, sp, list);
			vca_close_session(sp, "ERR");
			SES_Delete(sp);
		} else if (ep->events & EPOLLHUP) {
			VTAILQ_REMOVE(&sesshead, sp, list);
			vca_close_session(sp, "HUP");
			SES_Delete(sp);
		} else if (ep->events & EPOLLRDHUP) {
			VTAILQ_REMOVE(&sesshead, sp, list);
			vca_close_session(sp, "RHUP");
			SES_Delete(sp);
		}
	}
}