1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
24  */
25 
26 /*
27  * Copyright (c) 2018, Joyent, Inc.
28  */
29 
30 /*
31  * FMA event subscription interfaces - subscribe to FMA protocol
32  * from outside the fault manager.
33  */
34 
35 #include <sys/types.h>
36 #include <atomic.h>
37 #include <libsysevent.h>
38 #include <libuutil.h>
39 #include <pthread.h>
40 #include <stdarg.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <strings.h>
44 #include <unistd.h>
45 #include <fm/libtopo.h>
46 
47 #include <fm/libfmevent.h>
48 
49 #include "fmev_impl.h"
50 
51 static topo_hdl_t *g_topohdl;
52 
53 typedef struct {
54 	struct fmev_hdl_cmn sh_cmn;
55 	evchan_t *sh_binding;
56 	uu_avl_pool_t *sh_pool;
57 	uu_avl_t *sh_avl;
58 	uint32_t sh_subcnt;
59 	uint32_t sh_flags;
60 	sysevent_subattr_t *sh_attr;
61 	pthread_mutex_t sh_lock;
62 	pthread_mutex_t sh_srlz_lock;
63 } fmev_shdl_impl_t;
64 
65 #define	HDL2IHDL(hdl)	((fmev_shdl_impl_t *)(hdl))
66 #define	IHDL2HDL(ihdl)	((fmev_shdl_t)(ihdl))
67 
68 #define	_FMEV_SHMAGIC	0x5368446c	/* ShDl */
69 #define	FMEV_SHDL_VALID(ihdl)	((ihdl)->sh_cmn.hc_magic == _FMEV_SHMAGIC)
70 
71 #define	SHDL_FL_SERIALIZE	0x1
72 
73 #define	FMEV_API_ENTER(hdl, v) \
74 	fmev_api_enter(&HDL2IHDL(hdl)->sh_cmn, LIBFMEVENT_VERSION_##v)
75 
76 /*
77  * For each subscription on a handle we add a node to an avl tree
78  * to track subscriptions.
79  */
80 
81 #define	FMEV_SID_SZ	(16 + 1)	/* Matches MAX_SUBID_LEN */
82 
83 struct fmev_subinfo {
84 	uu_avl_node_t si_node;
85 	fmev_shdl_impl_t *si_ihdl;
86 	char si_pat[FMEV_MAX_CLASS];
87 	char si_sid[FMEV_SID_SZ];
88 	fmev_cbfunc_t *si_cb;
89 	void *si_cbarg;
90 };
91 
92 struct fmev_hdl_cmn *
fmev_shdl_cmn(fmev_shdl_t hdl)93 fmev_shdl_cmn(fmev_shdl_t hdl)
94 {
95 	return (&HDL2IHDL(hdl)->sh_cmn);
96 }
97 
98 static int
shdlctl_start(fmev_shdl_impl_t * ihdl)99 shdlctl_start(fmev_shdl_impl_t *ihdl)
100 {
101 	(void) pthread_mutex_lock(&ihdl->sh_lock);
102 
103 	if (ihdl->sh_subcnt == 0) {
104 		return (1);	/* lock still held */
105 	} else {
106 		(void) pthread_mutex_unlock(&ihdl->sh_lock);
107 		return (0);
108 	}
109 }
110 
111 static void
shdlctl_end(fmev_shdl_impl_t * ihdl)112 shdlctl_end(fmev_shdl_impl_t *ihdl)
113 {
114 	(void) pthread_mutex_unlock(&ihdl->sh_lock);
115 }
116 
117 fmev_err_t
fmev_shdlctl_serialize(fmev_shdl_t hdl)118 fmev_shdlctl_serialize(fmev_shdl_t hdl)
119 {
120 	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);
121 
122 	if (!FMEV_API_ENTER(hdl, 1))
123 		return (fmev_errno);
124 
125 	if (!shdlctl_start(ihdl))
126 		return (fmev_seterr(FMEVERR_BUSY));
127 
128 	if (!(ihdl->sh_flags & SHDL_FL_SERIALIZE)) {
129 		(void) pthread_mutex_init(&ihdl->sh_srlz_lock, NULL);
130 		ihdl->sh_flags |= SHDL_FL_SERIALIZE;
131 	}
132 
133 	shdlctl_end(ihdl);
134 	return (fmev_seterr(FMEV_SUCCESS));
135 }
136 
137 fmev_err_t
fmev_shdlctl_thrattr(fmev_shdl_t hdl,pthread_attr_t * attr)138 fmev_shdlctl_thrattr(fmev_shdl_t hdl, pthread_attr_t *attr)
139 {
140 	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);
141 
142 	if (!FMEV_API_ENTER(hdl, 1))
143 		return (fmev_errno);
144 
145 	if (!shdlctl_start(ihdl))
146 		return (fmev_seterr(FMEVERR_BUSY));
147 
148 	sysevent_subattr_thrattr(ihdl->sh_attr, attr);
149 
150 	shdlctl_end(ihdl);
151 	return (fmev_seterr(FMEV_SUCCESS));
152 }
153 
154 fmev_err_t
fmev_shdlctl_sigmask(fmev_shdl_t hdl,sigset_t * set)155 fmev_shdlctl_sigmask(fmev_shdl_t hdl, sigset_t *set)
156 {
157 	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);
158 
159 	if (!FMEV_API_ENTER(hdl, 1))
160 		return (fmev_errno);
161 
162 	if (!shdlctl_start(ihdl))
163 		return (fmev_seterr(FMEVERR_BUSY));
164 
165 	sysevent_subattr_sigmask(ihdl->sh_attr, set);
166 
167 	shdlctl_end(ihdl);
168 	return (fmev_seterr(FMEV_SUCCESS));
169 }
170 
171 fmev_err_t
fmev_shdlctl_thrsetup(fmev_shdl_t hdl,door_xcreate_thrsetup_func_t * func,void * cookie)172 fmev_shdlctl_thrsetup(fmev_shdl_t hdl, door_xcreate_thrsetup_func_t *func,
173     void *cookie)
174 {
175 	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);
176 
177 	if (!FMEV_API_ENTER(hdl, 1))
178 		return (fmev_errno);
179 
180 	if (!shdlctl_start(ihdl))
181 		return (fmev_seterr(FMEVERR_BUSY));
182 
183 	sysevent_subattr_thrsetup(ihdl->sh_attr, func, cookie);
184 
185 	shdlctl_end(ihdl);
186 	return (fmev_seterr(FMEV_SUCCESS));
187 }
188 
189 fmev_err_t
fmev_shdlctl_thrcreate(fmev_shdl_t hdl,door_xcreate_server_func_t * func,void * cookie)190 fmev_shdlctl_thrcreate(fmev_shdl_t hdl, door_xcreate_server_func_t *func,
191     void *cookie)
192 {
193 	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);
194 
195 	if (!FMEV_API_ENTER(hdl, 1))
196 		return (fmev_errno);
197 
198 	if (!shdlctl_start(ihdl))
199 		return (fmev_seterr(FMEVERR_BUSY));
200 
201 	sysevent_subattr_thrcreate(ihdl->sh_attr, func, cookie);
202 
203 	shdlctl_end(ihdl);
204 	return (fmev_seterr(FMEV_SUCCESS));
205 }
206 
207 /*
208  * Our door service function.  We return 0 regardless so that the kernel
209  * does not keep either retrying (EAGAIN) or bleat to cmn_err.
210  */
211 
212 uint64_t fmev_proxy_cb_enomem;
213 
214 static int
fmev_proxy_cb(sysevent_t * sep,void * arg)215 fmev_proxy_cb(sysevent_t *sep, void *arg)
216 {
217 	struct fmev_subinfo *sip = arg;
218 	fmev_shdl_impl_t *ihdl = sip->si_ihdl;
219 	nvlist_t *nvl;
220 	char *class;
221 	fmev_t ev;
222 
223 	if ((ev = fmev_sysev2fmev(IHDL2HDL(ihdl), sep, &class, &nvl)) == NULL) {
224 		fmev_proxy_cb_enomem++;
225 		return (0);
226 	}
227 
228 	if (ihdl->sh_flags & SHDL_FL_SERIALIZE)
229 		(void) pthread_mutex_lock(&ihdl->sh_srlz_lock);
230 
231 	sip->si_cb(ev, class, nvl, sip->si_cbarg);
232 
233 	if (ihdl->sh_flags & SHDL_FL_SERIALIZE)
234 		(void) pthread_mutex_unlock(&ihdl->sh_srlz_lock);
235 
236 	fmev_rele(ev);	/* release hold obtained in fmev_sysev2fmev */
237 
238 	return (0);
239 }
240 
241 static volatile uint32_t fmev_subid;
242 
243 fmev_err_t
fmev_shdl_subscribe(fmev_shdl_t hdl,const char * pat,fmev_cbfunc_t func,void * funcarg)244 fmev_shdl_subscribe(fmev_shdl_t hdl, const char *pat, fmev_cbfunc_t func,
245     void *funcarg)
246 {
247 	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);
248 	struct fmev_subinfo *sip;
249 	uu_avl_index_t idx;
250 	uint64_t nsid;
251 	int serr;
252 
253 	if (!FMEV_API_ENTER(hdl, 1))
254 		return (fmev_errno);
255 
256 	if (pat == NULL || func == NULL)
257 		return (fmev_seterr(FMEVERR_API));
258 
259 	/*
260 	 * Empty class patterns are illegal, as is the sysevent magic for
261 	 * all classes.  Also validate class length.
262 	 */
263 	if (*pat == '\0' || strncmp(pat, EC_ALL, sizeof (EC_ALL)) == 0 ||
264 	    strncmp(pat, EC_SUB_ALL, sizeof (EC_SUB_ALL)) == 0 ||
265 	    strnlen(pat, FMEV_MAX_CLASS) == FMEV_MAX_CLASS)
266 		return (fmev_seterr(FMEVERR_BADCLASS));
267 
268 	if ((sip = fmev_shdl_zalloc(hdl, sizeof (*sip))) == NULL)
269 		return (fmev_seterr(FMEVERR_ALLOC));
270 
271 	(void) strncpy(sip->si_pat, pat, sizeof (sip->si_pat));
272 
273 	uu_avl_node_init(sip, &sip->si_node, ihdl->sh_pool);
274 
275 	(void) pthread_mutex_lock(&ihdl->sh_lock);
276 
277 	if (uu_avl_find(ihdl->sh_avl, sip, NULL, &idx) != NULL) {
278 		(void) pthread_mutex_unlock(&ihdl->sh_lock);
279 		fmev_shdl_free(hdl, sip, sizeof (*sip));
280 		return (fmev_seterr(FMEVERR_DUPLICATE));
281 	}
282 
283 	/*
284 	 * Generate a subscriber id for GPEC that is unique to this
285 	 * subscription.  There is no provision for persistent
286 	 * subscribers.  The subscriber id must be unique within
287 	 * this zone.
288 	 */
289 	nsid = (uint64_t)getpid() << 32 | atomic_inc_32_nv(&fmev_subid);
290 	(void) snprintf(sip->si_sid, sizeof (sip->si_sid), "%llx", nsid);
291 
292 	sip->si_ihdl = ihdl;
293 	sip->si_cb = func;
294 	sip->si_cbarg = funcarg;
295 
296 	if ((serr = sysevent_evc_xsubscribe(ihdl->sh_binding, sip->si_sid,
297 	    sip->si_pat, fmev_proxy_cb, sip, 0, ihdl->sh_attr)) != 0) {
298 		fmev_err_t err;
299 
300 		(void) pthread_mutex_unlock(&ihdl->sh_lock);
301 		fmev_shdl_free(hdl, sip, sizeof (*sip));
302 
303 		switch (serr) {
304 		case ENOMEM:
305 			err = FMEVERR_MAX_SUBSCRIBERS;
306 			break;
307 
308 		default:
309 			err = FMEVERR_INTERNAL;
310 			break;
311 		}
312 
313 		return (fmev_seterr(err));
314 	}
315 
316 	uu_avl_insert(ihdl->sh_avl, sip, idx);
317 	ihdl->sh_subcnt++;
318 
319 	(void) pthread_mutex_unlock(&ihdl->sh_lock);
320 
321 	return (fmev_seterr(FMEV_SUCCESS));
322 }
323 
324 static int
fmev_subinfo_fini(fmev_shdl_impl_t * ihdl,struct fmev_subinfo * sip,boolean_t doavl)325 fmev_subinfo_fini(fmev_shdl_impl_t *ihdl, struct fmev_subinfo *sip,
326     boolean_t doavl)
327 {
328 	int err;
329 
330 	ASSERT(sip->si_ihdl == ihdl);
331 
332 	err = sysevent_evc_unsubscribe(ihdl->sh_binding, sip->si_sid);
333 
334 	if (err == 0) {
335 		if (doavl) {
336 			uu_avl_remove(ihdl->sh_avl, sip);
337 			uu_avl_node_fini(sip, &sip->si_node, ihdl->sh_pool);
338 		}
339 		fmev_shdl_free(IHDL2HDL(ihdl), sip, sizeof (*sip));
340 		ihdl->sh_subcnt--;
341 	}
342 
343 	return (err);
344 }
345 
346 fmev_err_t
fmev_shdl_unsubscribe(fmev_shdl_t hdl,const char * pat)347 fmev_shdl_unsubscribe(fmev_shdl_t hdl, const char *pat)
348 {
349 	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);
350 	fmev_err_t rv = FMEVERR_NOMATCH;
351 	struct fmev_subinfo *sip;
352 	struct fmev_subinfo si;
353 	int err;
354 
355 	if (!FMEV_API_ENTER(hdl, 1))
356 		return (fmev_errno);
357 
358 	if (pat == NULL)
359 		return (fmev_seterr(FMEVERR_API));
360 
361 	if (*pat == '\0' || strncmp(pat, EVCH_ALLSUB, sizeof (EC_ALL)) == 0 ||
362 	    strnlen(pat, FMEV_MAX_CLASS) == FMEV_MAX_CLASS)
363 		return (fmev_seterr(FMEVERR_BADCLASS));
364 
365 	(void) strncpy(si.si_pat, pat, sizeof (si.si_pat));
366 
367 	(void) pthread_mutex_lock(&ihdl->sh_lock);
368 
369 	if ((sip = uu_avl_find(ihdl->sh_avl, &si, NULL, NULL)) != NULL) {
370 		if ((err = fmev_subinfo_fini(ihdl, sip, B_TRUE)) == 0) {
371 			rv = FMEV_SUCCESS;
372 		} else {
373 			/*
374 			 * Return an API error if the unsubscribe was
375 			 * attempted from within a door callback invocation;
376 			 * other errors should not happen.
377 			 */
378 			rv = (err == EDEADLK) ? FMEVERR_API : FMEVERR_INTERNAL;
379 		}
380 	}
381 
382 	(void) pthread_mutex_unlock(&ihdl->sh_lock);
383 
384 	return (fmev_seterr(rv));
385 }
386 
387 void *
fmev_shdl_alloc(fmev_shdl_t hdl,size_t sz)388 fmev_shdl_alloc(fmev_shdl_t hdl, size_t sz)
389 {
390 	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);
391 
392 	if (!FMEV_API_ENTER(hdl, 1))
393 		return (NULL);
394 
395 	return (ihdl->sh_cmn.hc_alloc(sz));
396 }
397 
398 void *
fmev_shdl_zalloc(fmev_shdl_t hdl,size_t sz)399 fmev_shdl_zalloc(fmev_shdl_t hdl, size_t sz)
400 {
401 	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);
402 
403 	if (!FMEV_API_ENTER(hdl, 1))
404 		return (NULL);
405 
406 	return (ihdl->sh_cmn.hc_zalloc(sz));
407 }
408 
409 void
fmev_shdl_free(fmev_shdl_t hdl,void * buf,size_t sz)410 fmev_shdl_free(fmev_shdl_t hdl, void *buf, size_t sz)
411 {
412 	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);
413 
414 	if (!FMEV_API_ENTER(hdl, 1))
415 		return;
416 
417 	ihdl->sh_cmn.hc_free(buf, sz);
418 }
419 
420 char *
fmev_shdl_strdup(fmev_shdl_t hdl,char * src)421 fmev_shdl_strdup(fmev_shdl_t hdl, char *src)
422 {
423 	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);
424 	size_t srclen;
425 	char *dst;
426 
427 	if (!FMEV_API_ENTER(hdl, 2))
428 		return (NULL);
429 
430 	srclen = strlen(src);
431 
432 	if ((dst = ihdl->sh_cmn.hc_alloc(srclen + 1)) == NULL) {
433 		(void) fmev_seterr(FMEVERR_ALLOC);
434 		return (NULL);
435 	}
436 
437 	(void) strncpy(dst, src, srclen);
438 	dst[srclen] = '\0';
439 	return (dst);
440 }
441 
442 void
fmev_shdl_strfree(fmev_shdl_t hdl,char * buf)443 fmev_shdl_strfree(fmev_shdl_t hdl, char *buf)
444 {
445 	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);
446 
447 	(void) FMEV_API_ENTER(hdl, 2);
448 
449 	ihdl->sh_cmn.hc_free(buf, strlen(buf) + 1);
450 }
451 
452 int
fmev_shdl_valid(fmev_shdl_t hdl)453 fmev_shdl_valid(fmev_shdl_t hdl)
454 {
455 	return (FMEV_SHDL_VALID(HDL2IHDL(hdl)));
456 }
457 
458 /*ARGSUSED*/
459 static int
fmev_keycmp(const void * l,const void * r,void * arg)460 fmev_keycmp(const void *l, const void *r, void *arg)
461 {
462 	struct fmev_subinfo *left = (struct fmev_subinfo *)l;
463 	struct fmev_subinfo *right = (struct fmev_subinfo *)r;
464 
465 	return (strncmp(left->si_pat, right->si_pat, FMEV_MAX_CLASS));
466 }
467 
468 fmev_shdl_t
fmev_shdl_init(uint32_t caller_version,void * (* hdlalloc)(size_t),void * (* hdlzalloc)(size_t),void (* hdlfree)(void *,size_t))469 fmev_shdl_init(uint32_t caller_version, void *(*hdlalloc)(size_t),
470     void *(*hdlzalloc)(size_t), void (*hdlfree)(void *, size_t))
471 {
472 	fmev_shdl_impl_t *ihdl;
473 	struct fmev_hdl_cmn hc;
474 	const char *chan_name;
475 	int err;
476 
477 	hc.hc_magic = _FMEV_SHMAGIC;
478 	hc.hc_api_vers = caller_version;
479 	hc.hc_alloc = hdlalloc ? hdlalloc : dflt_alloc;
480 	hc.hc_zalloc = hdlzalloc ? hdlzalloc : dflt_zalloc;
481 	hc.hc_free = hdlfree ? hdlfree : dflt_free;
482 
483 	if (!fmev_api_init(&hc))
484 		return (NULL);	/* error type set */
485 
486 	if (!((hdlalloc == NULL && hdlzalloc == NULL && hdlfree == NULL) ||
487 	    (hdlalloc != NULL && hdlzalloc != NULL && hdlfree != NULL))) {
488 		(void) fmev_seterr(FMEVERR_API);
489 		return (NULL);
490 	}
491 
492 	if (hdlzalloc == NULL)
493 		ihdl = dflt_zalloc(sizeof (*ihdl));
494 	else
495 		ihdl = hdlzalloc(sizeof (*ihdl));
496 
497 	if (ihdl == NULL) {
498 		(void) fmev_seterr(FMEVERR_ALLOC);
499 		return (NULL);
500 	}
501 
502 	ihdl->sh_cmn = hc;
503 
504 	if ((ihdl->sh_attr = sysevent_subattr_alloc()) == NULL) {
505 		err = FMEVERR_ALLOC;
506 		goto error;
507 	}
508 
509 	(void) pthread_mutex_init(&ihdl->sh_lock, NULL);
510 
511 	/*
512 	 * For simulation purposes we allow an environment variable
513 	 * to provide a different channel name.
514 	 */
515 	if ((chan_name = getenv("FMD_SNOOP_CHANNEL")) == NULL)
516 		chan_name = FMD_SNOOP_CHANNEL;
517 
518 	/*
519 	 * Try to bind to the event channel. If it's not already present,
520 	 * attempt to create the channel so that we can startup before
521 	 * the event producer (who will also apply choices such as
522 	 * channel depth when they bind to the channel).
523 	 */
524 	if (sysevent_evc_bind(chan_name, &ihdl->sh_binding,
525 	    EVCH_CREAT | EVCH_HOLD_PEND_INDEF) != 0) {
526 		switch (errno) {
527 		case EINVAL:
528 		default:
529 			err = FMEVERR_INTERNAL;
530 			break;
531 		case ENOMEM:
532 			err = FMEVERR_ALLOC;
533 			break;
534 		case EPERM:
535 			err = FMEVERR_NOPRIV;
536 			break;
537 		}
538 		goto error;
539 	}
540 
541 	if ((ihdl->sh_pool = uu_avl_pool_create("subinfo_pool",
542 	    sizeof (struct fmev_subinfo),
543 	    offsetof(struct fmev_subinfo, si_node), fmev_keycmp,
544 	    UU_AVL_POOL_DEBUG)) == NULL) {
545 		err = FMEVERR_INTERNAL;
546 		goto error;
547 	}
548 
549 	if ((ihdl->sh_avl = uu_avl_create(ihdl->sh_pool, NULL,
550 	    UU_DEFAULT)) == NULL) {
551 		err = FMEVERR_INTERNAL;
552 		goto error;
553 	}
554 
555 	return (IHDL2HDL(ihdl));
556 
557 error:
558 	(void) fmev_shdl_fini(IHDL2HDL(ihdl));
559 	(void) fmev_seterr(err);
560 	return (NULL);
561 }
562 
563 fmev_err_t
fmev_shdl_getauthority(fmev_shdl_t hdl,nvlist_t ** nvlp)564 fmev_shdl_getauthority(fmev_shdl_t hdl, nvlist_t **nvlp)
565 {
566 	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);
567 	nvlist_t *propnvl;
568 	fmev_err_t rc;
569 
570 	if (!FMEV_API_ENTER(hdl, 2))
571 		return (fmev_errno);
572 
573 	(void) pthread_mutex_lock(&ihdl->sh_lock);
574 
575 	if (sysevent_evc_getpropnvl(ihdl->sh_binding, &propnvl) != 0) {
576 		*nvlp = NULL;
577 		(void) pthread_mutex_unlock(&ihdl->sh_lock);
578 		return (fmev_seterr(FMEVERR_UNKNOWN));
579 	}
580 
581 	if (propnvl == NULL) {
582 		rc = FMEVERR_BUSY;	/* Other end has not bound */
583 	} else {
584 		nvlist_t *auth;
585 
586 		if (nvlist_lookup_nvlist(propnvl, "fmdauth", &auth) == 0) {
587 			rc = (nvlist_dup(auth, nvlp, 0) == 0) ? FMEV_SUCCESS :
588 			    FMEVERR_ALLOC;
589 		} else {
590 			rc = FMEVERR_INTERNAL;
591 		}
592 		nvlist_free(propnvl);
593 	}
594 
595 	(void) pthread_mutex_unlock(&ihdl->sh_lock);
596 
597 	if (rc != FMEV_SUCCESS) {
598 		*nvlp = NULL;
599 		(void) fmev_seterr(rc);
600 	}
601 
602 	return (rc);
603 }
604 
605 char *
fmev_shdl_nvl2str(fmev_shdl_t hdl,nvlist_t * nvl)606 fmev_shdl_nvl2str(fmev_shdl_t hdl, nvlist_t *nvl)
607 {
608 	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);
609 	char *fmri, *fmricp;
610 	fmev_err_t err;
611 	int topoerr;
612 
613 	if (!FMEV_API_ENTER(hdl, 2))
614 		return (NULL);
615 
616 	if (g_topohdl == NULL) {
617 		(void) pthread_mutex_lock(&ihdl->sh_lock);
618 		if (g_topohdl == NULL)
619 			g_topohdl = topo_open(TOPO_VERSION, NULL, &topoerr);
620 		(void) pthread_mutex_unlock(&ihdl->sh_lock);
621 
622 		if (g_topohdl == NULL) {
623 			(void) fmev_seterr(FMEVERR_INTERNAL);
624 			return (NULL);
625 		}
626 	}
627 
628 	if (topo_fmri_nvl2str(g_topohdl, nvl, &fmri, &topoerr) == 0) {
629 		fmricp = fmev_shdl_strdup(hdl, fmri);
630 		topo_hdl_strfree(g_topohdl, fmri);
631 		return (fmricp);	/* fmev_errno set if strdup failed */
632 	}
633 
634 	switch (topoerr) {
635 	case ETOPO_FMRI_NOMEM:
636 		err = FMEVERR_ALLOC;
637 		break;
638 
639 	case ETOPO_FMRI_MALFORM:
640 	case ETOPO_METHOD_NOTSUP:
641 	case ETOPO_METHOD_INVAL:
642 	default:
643 		err = FMEVERR_INVALIDARG;
644 		break;
645 	}
646 
647 	(void) fmev_seterr(err);
648 	return (NULL);
649 }
650 
651 fmev_err_t
fmev_shdl_fini(fmev_shdl_t hdl)652 fmev_shdl_fini(fmev_shdl_t hdl)
653 {
654 	fmev_shdl_impl_t *ihdl = HDL2IHDL(hdl);
655 
656 	if (!FMEV_API_ENTER(hdl, 1))
657 		return (fmev_errno);
658 
659 	(void) pthread_mutex_lock(&ihdl->sh_lock);
660 
661 	/*
662 	 * Verify that we are not in callback context - return an API
663 	 * error if we are.
664 	 */
665 	if (sysevent_evc_unsubscribe(ihdl->sh_binding, "invalidsid") ==
666 	    EDEADLK) {
667 		(void) pthread_mutex_unlock(&ihdl->sh_lock);
668 		return (fmev_seterr(FMEVERR_API));
669 	}
670 
671 	if (ihdl->sh_avl) {
672 		void *cookie = NULL;
673 		struct fmev_subinfo *sip;
674 
675 		while ((sip = uu_avl_teardown(ihdl->sh_avl, &cookie)) != NULL)
676 			(void) fmev_subinfo_fini(ihdl, sip, B_FALSE);
677 
678 		uu_avl_destroy(ihdl->sh_avl);
679 		ihdl->sh_avl = NULL;
680 	}
681 
682 	ASSERT(ihdl->sh_subcnt == 0);
683 
684 	if (ihdl->sh_binding) {
685 		(void) sysevent_evc_unbind(ihdl->sh_binding);
686 		ihdl->sh_binding = NULL;
687 	}
688 
689 	if (ihdl->sh_pool) {
690 		uu_avl_pool_destroy(ihdl->sh_pool);
691 		ihdl->sh_pool = NULL;
692 	}
693 
694 	if (ihdl->sh_attr) {
695 		sysevent_subattr_free(ihdl->sh_attr);
696 		ihdl->sh_attr = NULL;
697 	}
698 
699 	ihdl->sh_cmn.hc_magic = 0;
700 
701 	if (g_topohdl) {
702 		topo_close(g_topohdl);
703 		g_topohdl = NULL;
704 	}
705 
706 	(void) pthread_mutex_unlock(&ihdl->sh_lock);
707 	(void) pthread_mutex_destroy(&ihdl->sh_lock);
708 
709 	fmev_shdl_free(hdl, hdl, sizeof (*ihdl));
710 
711 	fmev_api_freetsd();
712 
713 	return (fmev_seterr(FMEV_SUCCESS));
714 }
715