view usr/src/lib/fm/libfmevent/common/fmev_subscribe.c @ 12979:ab9ae749152f

PSARC/2009/617 Software Events Notification Parameters CLI PSARC/2009/618 snmp-notify: SNMP Notification Daemon for Software Events PSARC/2009/619 smtp-notify: Email Notification Daemon for Software Events PSARC/2010/225 fmd for non-global Solaris zones PSARC/2010/226 Solaris Instance UUID PSARC/2010/227 nvlist_nvflag(3NVPAIR) PSARC/2010/228 libfmevent additions PSARC/2010/257 sysevent_evc_setpropnvl and sysevent_evc_getpropnvl PSARC/2010/265 FMRI and FMA Event Stabilty, 'ireport' category 1 event class, and the 'sw' FMRI scheme PSARC/2010/278 FMA/SMF integration: instance state transitions PSARC/2010/279 Modelling panics within FMA PSARC/2010/290 logadm.conf upgrade 6392476 fmdump needs to pretty-print 6393375 userland ereport/ireport event generation interfaces 6445732 Add email notification agent for FMA and software events 6804168 RFE: Allow an efficient means to monitor SMF services status changes 6866661 scf_values_destroy(3SCF) will segfault if is passed NULL 6884709 Add snmp notification agent for FMA and software events 6884712 Add private interface to tap into libfmd_msg macro expansion capabilities 6897919 fmd to run in a non-global zone 6897937 fmd use of non-private doors is not safe 6900081 add a UUID to Solaris kernel image for use in crashdump identification 6914884 model panic events as a defect diagnosis in FMA 6944862 fmd_case_open_uuid, fmd_case_uuisresolved, fmd_nvl_create_defect 6944866 log legacy sysevents in fmd 6944867 enumerate svc scheme in topo 6944868 software-diagnosis and software-response fmd modules 6944870 model SMF maintenance state as a defect diagnosis in FMA 6944876 savecore runs in foreground for systems with zfs root and dedicated dump 6965796 Implement notification parameters for SMF state transitions and FMA events 6968287 SUN-FM-MIB.mib needs to be updated to reflect Oracle information 6972331 logadm.conf upgrade PSARC/2010/290
author Gavin Maltby <gavin.maltby@oracle.com>
date Fri, 30 Jul 2010 17:04:17 +1000
parents b91faef0c984
children
line wrap: on
line source

/*
 * 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 (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * FMA event subscription interfaces - subscribe to FMA protocol
 * from outside the fault manager.
 */

#include <sys/types.h>
#include <atomic.h>
#include <libsysevent.h>
#include <libuutil.h>
#include <pthread.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <fm/libtopo.h>

#include <fm/libfmevent.h>

#include "fmev_impl.h"

static topo_hdl_t *g_topohdl;

typedef struct {
	struct fmev_hdl_cmn sh_cmn;
	evchan_t *sh_binding;
	uu_avl_pool_t *sh_pool;
	uu_avl_t *sh_avl;
	uint32_t sh_subcnt;
	uint32_t sh_flags;
	sysevent_subattr_t *sh_attr;
	pthread_mutex_t sh_lock;
	pthread_mutex_t sh_srlz_lock;
} fmev_shdl_impl_t;

#define	HDL2IHDL(hdl)	((fmev_shdl_impl_t *)(hdl))
#define	IHDL2HDL(ihdl)	((fmev_shdl_t)(ihdl))

#define	_FMEV_SHMAGIC	0x5368446c	/* ShDl */
#define	FMEV_SHDL_VALID(ihdl)	((ihdl)->sh_cmn.hc_magic == _FMEV_SHMAGIC)

#define	SHDL_FL_SERIALIZE	0x1

#define	FMEV_API_ENTER(hdl, v) \
	fmev_api_enter(&HDL2IHDL(hdl)->sh_cmn, LIBFMEVENT_VERSION_##v)

/*
 * For each subscription on a handle we add a node to an avl tree
 * to track subscriptions.
 */

#define	FMEV_SID_SZ	(16 + 1)	/* Matches MAX_SUBID_LEN */

struct fmev_subinfo {
	uu_avl_node_t si_node;
	fmev_shdl_impl_t *si_ihdl;
	char si_pat[FMEV_MAX_CLASS];
	char si_sid[FMEV_SID_SZ];
	fmev_cbfunc_t *si_cb;
	void *si_cbarg;
};

struct fmev_hdl_cmn *
fmev_shdl_cmn(fmev_shdl_t hdl)
{
	return (&HDL2IHDL(hdl)->sh_cmn);
}

static int
shdlctl_start(fmev_shdl_impl_t *ihdl)
{
	(void) pthread_mutex_lock(&ihdl->sh_lock);

	if (ihdl->sh_subcnt == 0) {
		return (1);	/* lock still held */
	} else {
		(void) pthread_mutex_unlock(&ihdl->sh_lock);
		return (0);
	}
}

static void
shdlctl_end(fmev_shdl_impl_t *ihdl)
{
	(void) pthread_mutex_unlock(&ihdl->sh_lock);
}

fmev_err_t
fmev_shdlctl_serialize(fmev_shdl_t hdl)
{
	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);

	if (!FMEV_API_ENTER(hdl, 1))
		return (fmev_errno);

	if (!shdlctl_start(ihdl))
		return (fmev_seterr(FMEVERR_BUSY));

	if (!(ihdl->sh_flags & SHDL_FL_SERIALIZE)) {
		(void) pthread_mutex_init(&ihdl->sh_srlz_lock, NULL);
		ihdl->sh_flags |= SHDL_FL_SERIALIZE;
	}

	shdlctl_end(ihdl);
	return (fmev_seterr(FMEV_SUCCESS));
}

fmev_err_t
fmev_shdlctl_thrattr(fmev_shdl_t hdl, pthread_attr_t *attr)
{
	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);

	if (!FMEV_API_ENTER(hdl, 1))
		return (fmev_errno);

	if (!shdlctl_start(ihdl))
		return (fmev_seterr(FMEVERR_BUSY));

	sysevent_subattr_thrattr(ihdl->sh_attr, attr);

	shdlctl_end(ihdl);
	return (fmev_seterr(FMEV_SUCCESS));
}

fmev_err_t
fmev_shdlctl_sigmask(fmev_shdl_t hdl, sigset_t *set)
{
	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);

	if (!FMEV_API_ENTER(hdl, 1))
		return (fmev_errno);

	if (!shdlctl_start(ihdl))
		return (fmev_seterr(FMEVERR_BUSY));

	sysevent_subattr_sigmask(ihdl->sh_attr, set);

	shdlctl_end(ihdl);
	return (fmev_seterr(FMEV_SUCCESS));
}

fmev_err_t
fmev_shdlctl_thrsetup(fmev_shdl_t hdl, door_xcreate_thrsetup_func_t *func,
    void *cookie)
{
	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);

	if (!FMEV_API_ENTER(hdl, 1))
		return (fmev_errno);

	if (!shdlctl_start(ihdl))
		return (fmev_seterr(FMEVERR_BUSY));

	sysevent_subattr_thrsetup(ihdl->sh_attr, func, cookie);

	shdlctl_end(ihdl);
	return (fmev_seterr(FMEV_SUCCESS));
}

fmev_err_t
fmev_shdlctl_thrcreate(fmev_shdl_t hdl, door_xcreate_server_func_t *func,
    void *cookie)
{
	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);

	if (!FMEV_API_ENTER(hdl, 1))
		return (fmev_errno);

	if (!shdlctl_start(ihdl))
		return (fmev_seterr(FMEVERR_BUSY));

	sysevent_subattr_thrcreate(ihdl->sh_attr, func, cookie);

	shdlctl_end(ihdl);
	return (fmev_seterr(FMEV_SUCCESS));
}

/*
 * Our door service function.  We return 0 regardless so that the kernel
 * does not keep either retrying (EAGAIN) or bleat to cmn_err.
 */

uint64_t fmev_proxy_cb_inval;
uint64_t fmev_proxy_cb_enomem;

int
fmev_proxy_cb(sysevent_t *sep, void *arg)
{
	struct fmev_subinfo *sip = arg;
	fmev_shdl_impl_t *ihdl = sip->si_ihdl;
	nvlist_t *nvl;
	char *class;
	fmev_t ev;

	if (sip == NULL || sip->si_cb == NULL) {
		fmev_proxy_cb_inval++;
		return (0);
	}

	if ((ev = fmev_sysev2fmev(IHDL2HDL(ihdl), sep, &class, &nvl)) == NULL) {
		fmev_proxy_cb_enomem++;
		return (0);
	}

	if (ihdl->sh_flags & SHDL_FL_SERIALIZE)
		(void) pthread_mutex_lock(&ihdl->sh_srlz_lock);

	sip->si_cb(ev, class, nvl, sip->si_cbarg);

	if (ihdl->sh_flags & SHDL_FL_SERIALIZE)
		(void) pthread_mutex_unlock(&ihdl->sh_srlz_lock);

	fmev_rele(ev);	/* release hold obtained in fmev_sysev2fmev */

	return (0);
}

static volatile uint32_t fmev_subid;

fmev_err_t
fmev_shdl_subscribe(fmev_shdl_t hdl, const char *pat, fmev_cbfunc_t func,
    void *funcarg)
{
	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);
	struct fmev_subinfo *sip;
	uu_avl_index_t idx;
	uint64_t nsid;
	int serr;

	if (!FMEV_API_ENTER(hdl, 1))
		return (fmev_errno);

	if (pat == NULL || func == NULL)
		return (fmev_seterr(FMEVERR_API));

	/*
	 * Empty class patterns are illegal, as is the sysevent magic for
	 * all classes.  Also validate class length.
	 */
	if (*pat == '\0' || strncmp(pat, EC_ALL, sizeof (EC_ALL)) == 0 ||
	    strncmp(pat, EC_SUB_ALL, sizeof (EC_SUB_ALL)) == 0 ||
	    strnlen(pat, FMEV_MAX_CLASS) == FMEV_MAX_CLASS)
		return (fmev_seterr(FMEVERR_BADCLASS));

	if ((sip = fmev_shdl_zalloc(hdl, sizeof (*sip))) == NULL)
		return (fmev_seterr(FMEVERR_ALLOC));

	(void) strncpy(sip->si_pat, pat, sizeof (sip->si_pat));

	uu_avl_node_init(sip, &sip->si_node, ihdl->sh_pool);

	(void) pthread_mutex_lock(&ihdl->sh_lock);

	if (uu_avl_find(ihdl->sh_avl, sip, NULL, &idx) != NULL) {
		(void) pthread_mutex_unlock(&ihdl->sh_lock);
		fmev_shdl_free(hdl, sip, sizeof (*sip));
		return (fmev_seterr(FMEVERR_DUPLICATE));
	}

	/*
	 * Generate a subscriber id for GPEC that is unique to this
	 * subscription.  There is no provision for persistent
	 * subscribers.  The subscriber id must be unique within
	 * this zone.
	 */
	nsid = (uint64_t)getpid() << 32 | atomic_inc_32_nv(&fmev_subid);
	(void) snprintf(sip->si_sid, sizeof (sip->si_sid), "%llx", nsid);

	sip->si_ihdl = ihdl;
	sip->si_cb = func;
	sip->si_cbarg = funcarg;

	if ((serr = sysevent_evc_xsubscribe(ihdl->sh_binding, sip->si_sid,
	    sip->si_pat, fmev_proxy_cb, sip, 0, ihdl->sh_attr)) != 0) {
		fmev_err_t err;

		(void) pthread_mutex_unlock(&ihdl->sh_lock);
		fmev_shdl_free(hdl, sip, sizeof (*sip));

		switch (serr) {
		case ENOMEM:
			err = FMEVERR_MAX_SUBSCRIBERS;
			break;

		default:
			err = FMEVERR_INTERNAL;
			break;
		}

		return (fmev_seterr(err));
	}

	uu_avl_insert(ihdl->sh_avl, sip, idx);
	ihdl->sh_subcnt++;

	(void) pthread_mutex_unlock(&ihdl->sh_lock);

	return (fmev_seterr(FMEV_SUCCESS));
}

static int
fmev_subinfo_fini(fmev_shdl_impl_t *ihdl, struct fmev_subinfo *sip,
    boolean_t doavl)
{
	int err;

	ASSERT(sip->si_ihdl == ihdl);

	err = sysevent_evc_unsubscribe(ihdl->sh_binding, sip->si_sid);

	if (err == 0) {
		if (doavl) {
			uu_avl_remove(ihdl->sh_avl, sip);
			uu_avl_node_fini(sip, &sip->si_node, ihdl->sh_pool);
		}
		fmev_shdl_free(IHDL2HDL(ihdl), sip, sizeof (*sip));
		ihdl->sh_subcnt--;
	}

	return (err);
}

fmev_err_t
fmev_shdl_unsubscribe(fmev_shdl_t hdl, const char *pat)
{
	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);
	fmev_err_t rv = FMEVERR_NOMATCH;
	struct fmev_subinfo *sip;
	struct fmev_subinfo si;
	int err;

	if (!FMEV_API_ENTER(hdl, 1))
		return (fmev_errno);

	if (pat == NULL)
		return (fmev_seterr(FMEVERR_API));

	if (*pat == '\0' || strncmp(pat, EVCH_ALLSUB, sizeof (EC_ALL)) == 0 ||
	    strnlen(pat, FMEV_MAX_CLASS) == FMEV_MAX_CLASS)
		return (fmev_seterr(FMEVERR_BADCLASS));

	(void) strncpy(si.si_pat, pat, sizeof (si.si_pat));

	(void) pthread_mutex_lock(&ihdl->sh_lock);

	if ((sip = uu_avl_find(ihdl->sh_avl, &si, NULL, NULL)) != NULL) {
		if ((err = fmev_subinfo_fini(ihdl, sip, B_TRUE)) == 0) {
			rv = FMEV_SUCCESS;
		} else {
			/*
			 * Return an API error if the unsubscribe was
			 * attempted from within a door callback invocation;
			 * other errors should not happen.
			 */
			rv = (err == EDEADLK) ? FMEVERR_API : FMEVERR_INTERNAL;
		}
	}

	(void) pthread_mutex_unlock(&ihdl->sh_lock);

	return (fmev_seterr(rv));
}

void *
fmev_shdl_alloc(fmev_shdl_t hdl, size_t sz)
{
	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);

	if (!FMEV_API_ENTER(hdl, 1))
		return (NULL);

	return (ihdl->sh_cmn.hc_alloc(sz));
}

void *
fmev_shdl_zalloc(fmev_shdl_t hdl, size_t sz)
{
	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);

	if (!FMEV_API_ENTER(hdl, 1))
		return (NULL);

	return (ihdl->sh_cmn.hc_zalloc(sz));
}

void
fmev_shdl_free(fmev_shdl_t hdl, void *buf, size_t sz)
{
	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);

	if (!FMEV_API_ENTER(hdl, 1))
		return;

	ihdl->sh_cmn.hc_free(buf, sz);
}

char *
fmev_shdl_strdup(fmev_shdl_t hdl, char *src)
{
	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);
	size_t srclen;
	char *dst;

	if (!FMEV_API_ENTER(hdl, 2))
		return (NULL);

	srclen = strlen(src);

	if ((dst = ihdl->sh_cmn.hc_alloc(srclen + 1)) == NULL) {
		(void) fmev_seterr(FMEVERR_ALLOC);
		return (NULL);
	}

	(void) strncpy(dst, src, srclen);
	dst[srclen] = '\0';
	return (dst);
}

void
fmev_shdl_strfree(fmev_shdl_t hdl, char *buf)
{
	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);

	(void) FMEV_API_ENTER(hdl, 2);

	ihdl->sh_cmn.hc_free(buf, strlen(buf) + 1);
}

int
fmev_shdl_valid(fmev_shdl_t hdl)
{
	return (FMEV_SHDL_VALID(HDL2IHDL(hdl)));
}

/*ARGSUSED*/
static int
fmev_keycmp(const void *l, const void *r, void *arg)
{
	struct fmev_subinfo *left = (struct fmev_subinfo *)l;
	struct fmev_subinfo *right = (struct fmev_subinfo *)r;

	return (strncmp(left->si_pat, right->si_pat, FMEV_MAX_CLASS));
}

fmev_shdl_t
fmev_shdl_init(uint32_t caller_version, void *(*hdlalloc)(size_t),
    void *(*hdlzalloc)(size_t), void (*hdlfree)(void *, size_t))
{
	fmev_shdl_impl_t *ihdl;
	struct fmev_hdl_cmn hc;
	const char *chan_name;
	int err;

	hc.hc_magic = _FMEV_SHMAGIC;
	hc.hc_api_vers = caller_version;
	hc.hc_alloc = hdlalloc ? hdlalloc : dflt_alloc;
	hc.hc_zalloc = hdlzalloc ? hdlzalloc : dflt_zalloc;
	hc.hc_free = hdlfree ? hdlfree : dflt_free;

	if (!fmev_api_init(&hc))
		return (NULL);	/* error type set */

	if (!((hdlalloc == NULL && hdlzalloc == NULL && hdlfree == NULL) ||
	    (hdlalloc != NULL && hdlzalloc != NULL && hdlfree != NULL))) {
		(void) fmev_seterr(FMEVERR_API);
		return (NULL);
	}

	if (hdlzalloc == NULL)
		ihdl = dflt_zalloc(sizeof (*ihdl));
	else
		ihdl = hdlzalloc(sizeof (*ihdl));

	if (ihdl == NULL) {
		(void) fmev_seterr(FMEVERR_ALLOC);
		return (NULL);
	}

	ihdl->sh_cmn = hc;

	if ((ihdl->sh_attr = sysevent_subattr_alloc()) == NULL) {
		err = FMEVERR_ALLOC;
		goto error;
	}

	(void) pthread_mutex_init(&ihdl->sh_lock, NULL);

	/*
	 * For simulation purposes we allow an environment variable
	 * to provide a different channel name.
	 */
	if ((chan_name = getenv("FMD_SNOOP_CHANNEL")) == NULL)
		chan_name = FMD_SNOOP_CHANNEL;

	/*
	 * Try to bind to the event channel. If it's not already present,
	 * attempt to create the channel so that we can startup before
	 * the event producer (who will also apply choices such as
	 * channel depth when they bind to the channel).
	 */
	if (sysevent_evc_bind(chan_name, &ihdl->sh_binding,
	    EVCH_CREAT | EVCH_HOLD_PEND_INDEF) != 0) {
		switch (errno) {
		case EINVAL:
		default:
			err = FMEVERR_INTERNAL;
			break;
		case ENOMEM:
			err = FMEVERR_ALLOC;
			break;
		case EPERM:
			err = FMEVERR_NOPRIV;
			break;
		}
		goto error;
	}

	if ((ihdl->sh_pool = uu_avl_pool_create("subinfo_pool",
	    sizeof (struct fmev_subinfo),
	    offsetof(struct fmev_subinfo, si_node), fmev_keycmp,
	    UU_AVL_POOL_DEBUG)) == NULL) {
		err = FMEVERR_INTERNAL;
		goto error;
	}

	if ((ihdl->sh_avl = uu_avl_create(ihdl->sh_pool, NULL,
	    UU_DEFAULT)) == NULL) {
		err = FMEVERR_INTERNAL;
		goto error;
	}

	return (IHDL2HDL(ihdl));

error:
	(void) fmev_shdl_fini(IHDL2HDL(ihdl));
	(void) fmev_seterr(err);
	return (NULL);
}

fmev_err_t
fmev_shdl_getauthority(fmev_shdl_t hdl, nvlist_t **nvlp)
{
	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);
	nvlist_t *propnvl;
	fmev_err_t rc;

	if (!FMEV_API_ENTER(hdl, 2))
		return (fmev_errno);

	(void) pthread_mutex_lock(&ihdl->sh_lock);

	if (sysevent_evc_getpropnvl(ihdl->sh_binding, &propnvl) != 0) {
		*nvlp = NULL;
		(void) pthread_mutex_unlock(&ihdl->sh_lock);
		return (fmev_seterr(FMEVERR_UNKNOWN));
	}

	if (propnvl == NULL) {
		rc = FMEVERR_BUSY;	/* Other end has not bound */
	} else {
		nvlist_t *auth;

		if (nvlist_lookup_nvlist(propnvl, "fmdauth", &auth) == 0) {
			rc = (nvlist_dup(auth, nvlp, 0) == 0) ? FMEV_SUCCESS :
			    FMEVERR_ALLOC;
		} else {
			rc = FMEVERR_INTERNAL;
		}
		nvlist_free(propnvl);
	}

	(void) pthread_mutex_unlock(&ihdl->sh_lock);

	if (rc != FMEV_SUCCESS) {
		*nvlp = NULL;
		(void) fmev_seterr(rc);
	}

	return (rc);
}

char *
fmev_shdl_nvl2str(fmev_shdl_t hdl, nvlist_t *nvl)
{
	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);
	char *fmri, *fmricp;
	fmev_err_t err;
	int topoerr;

	if (!FMEV_API_ENTER(hdl, 2))
		return (NULL);

	if (g_topohdl == NULL) {
		(void) pthread_mutex_lock(&ihdl->sh_lock);
		if (g_topohdl == NULL)
			g_topohdl = topo_open(TOPO_VERSION, NULL, &topoerr);
		(void) pthread_mutex_unlock(&ihdl->sh_lock);

		if (g_topohdl == NULL) {
			(void) fmev_seterr(FMEVERR_INTERNAL);
			return (NULL);
		}
	}

	if (topo_fmri_nvl2str(g_topohdl, nvl, &fmri, &topoerr) == 0) {
		fmricp = fmev_shdl_strdup(hdl, fmri);
		topo_hdl_strfree(g_topohdl, fmri);
		return (fmricp);	/* fmev_errno set if strdup failed */
	}

	switch (topoerr) {
	case ETOPO_FMRI_NOMEM:
		err = FMEVERR_ALLOC;
		break;

	case ETOPO_FMRI_MALFORM:
	case ETOPO_METHOD_NOTSUP:
	case ETOPO_METHOD_INVAL:
	default:
		err = FMEVERR_INVALIDARG;
		break;
	}

	(void) fmev_seterr(err);
	return (NULL);
}

fmev_err_t
fmev_shdl_fini(fmev_shdl_t hdl)
{
	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);

	if (!FMEV_API_ENTER(hdl, 1))
		return (fmev_errno);

	(void) pthread_mutex_lock(&ihdl->sh_lock);

	/*
	 * Verify that we are not in callback context - return an API
	 * error if we are.
	 */
	if (sysevent_evc_unsubscribe(ihdl->sh_binding, "invalidsid") ==
	    EDEADLK) {
		(void) pthread_mutex_unlock(&ihdl->sh_lock);
		return (fmev_seterr(FMEVERR_API));
	}

	if (ihdl->sh_avl) {
		void *cookie = NULL;
		struct fmev_subinfo *sip;

		while ((sip = uu_avl_teardown(ihdl->sh_avl, &cookie)) != NULL)
			(void) fmev_subinfo_fini(ihdl, sip, B_FALSE);

		uu_avl_destroy(ihdl->sh_avl);
		ihdl->sh_avl = NULL;
	}

	ASSERT(ihdl->sh_subcnt == 0);

	if (ihdl->sh_binding) {
		(void) sysevent_evc_unbind(ihdl->sh_binding);
		ihdl->sh_binding = NULL;
	}

	if (ihdl->sh_pool) {
		uu_avl_pool_destroy(ihdl->sh_pool);
		ihdl->sh_pool = NULL;
	}

	if (ihdl->sh_attr) {
		sysevent_subattr_free(ihdl->sh_attr);
		ihdl->sh_attr = NULL;
	}

	ihdl->sh_cmn.hc_magic = 0;

	if (g_topohdl) {
		topo_close(g_topohdl);
		g_topohdl = NULL;
	}

	(void) pthread_mutex_unlock(&ihdl->sh_lock);
	(void) pthread_mutex_destroy(&ihdl->sh_lock);

	fmev_shdl_free(hdl, hdl, sizeof (*ihdl));

	fmev_api_freetsd();

	return (fmev_seterr(FMEV_SUCCESS));
}