/* * 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. */ /* * Copyright (c) 2018, Joyent, Inc. */ /* * FMA event subscription interfaces - subscribe to FMA protocol * from outside the fault manager. */ #include #include #include #include #include #include #include #include #include #include #include #include #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_enomem; static 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 ((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)); }