OpenSolaris_b135/lib/fm/libfmevent/common/fmev_subscribe.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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * 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 <umem.h>
#include <unistd.h>

#include <fm/libfmevent.h>

#include "fmev_impl.h"
#include "fmev_channels.h"

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	API_ENTERV1(hdl) \
	fmev_api_enter(&HDL2IHDL(hdl)->sh_cmn, LIBFMEVENT_VERSION_1)

/*
 * 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 (!API_ENTERV1(hdl))
		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 (!API_ENTERV1(hdl))
		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 (!API_ENTERV1(hdl))
		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 (!API_ENTERV1(hdl))
		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 (!API_ENTERV1(hdl))
		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 (!API_ENTERV1(hdl))
		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 (!API_ENTERV1(hdl))
		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));
}

static void *
dflt_alloc(size_t sz)
{
	return (umem_alloc(sz, UMEM_DEFAULT));
}

static void *
dflt_zalloc(size_t sz)
{
	return (umem_zalloc(sz, UMEM_DEFAULT));
}

static void
dflt_free(void *buf, size_t sz)
{
	umem_free(buf, sz);
}

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

	(void) API_ENTERV1(hdl);

	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);

	(void) API_ENTERV1(hdl);

	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);

	(void) API_ENTERV1(hdl);

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

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_fini(fmev_shdl_t hdl)
{
	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);

	(void) API_ENTERV1(hdl);

	(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;

	(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));
}