xref: /illumos-gate/usr/src/lib/fm/libfmevent/common/fmev_publish.c (revision efd4c9b63ad77503c101fc6c2ed8ba96c9d52964)
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) 2010, Oracle and/or its affiliates. All rights reserved.
24  */
25 
26 /*
27  * Simple-minded raw event publication from user context.  See extensive
28  * comments in libfmevent.h.  These interfaces remain Project Private -
29  * they have to evolve before rollout to Public levels.
30  *
31  * Events are dispatched synchronously using the GPEC sysevent mechanism.
32  * The caller context must therefore be one in which a sysevent_evc_publish
33  * (and possibly sysevent_evc_bind if not already bound) is safe.  We will
34  * also allocate and manipulate nvlists.
35  *
36  * Since we use GPEC, which has no least privilege awareness, these interfaces
37  * will only work for would-be producers running as root.
38  *
39  * There is no event rate throttling applied, so we rely on producers
40  * to throttle themselves.  A future refinement should apply mandatory
41  * but tuneable throttling on a per-producer basis.  In this first version
42  * the only throttle is the publication event queue depth - we'll drop
43  * events when the queue is full.
44  *
45  * We can publish over four channels, for privileged/non-privileged and
46  * high/low priority.  Since only privileged producers will work now
47  * (see above) we hardcode priv == B_TRUE and so only two channels are
48  * actually used, separating higher and lower value streams from privileged
49  * producers.
50  */
51 
52 #include <stdarg.h>
53 #include <unistd.h>
54 #include <stdlib.h>
55 #include <atomic.h>
56 #include <errno.h>
57 #include <pthread.h>
58 #include <strings.h>
59 
60 #include "fmev_impl.h"
61 
62 static struct {
63 	const char *name;		/* channel name */
64 	evchan_t *binding;		/* GPEC binding, once bound */
65 	const uint32_t flags;		/* flags to use in binding */
66 } chaninfo[] = {
67 	{ FMEV_CHAN_USER_NOPRIV_LV, NULL, 0 },
68 	{ FMEV_CHAN_USER_NOPRIV_HV, NULL, 0 },
69 	{ FMEV_CHAN_USER_PRIV_LV, NULL, EVCH_HOLD_PEND_INDEF },
70 	{ FMEV_CHAN_USER_PRIV_HV, NULL, EVCH_HOLD_PEND_INDEF}
71 };
72 
73 #define	CHANIDX(priv, pri) (2 * ((priv) != 0) + (pri == FMEV_HIPRI))
74 
75 #define	CHAN_NAME(priv, pri) (chaninfo[CHANIDX(priv, pri)].name)
76 #define	CHAN_BINDING(priv, pri) (chaninfo[CHANIDX(priv, pri)].binding)
77 #define	CHAN_FLAGS(priv, pri) (chaninfo[CHANIDX(priv, pri)].flags)
78 
79 /*
80  * Called after fork in the new child.  We clear the cached event
81  * channel bindings which are only valid in the process that created
82  * them.
83  */
84 static void
85 clear_bindings(void)
86 {
87 	int i;
88 
89 	for (i = 0; i < sizeof (chaninfo) / sizeof chaninfo[0]; i++)
90 		chaninfo[i].binding = NULL;
91 }
92 
93 #pragma init(_fmev_publish_init)
94 
95 static void
96 _fmev_publish_init(void)
97 {
98 	(void) pthread_atfork(NULL, NULL, clear_bindings);
99 }
100 
101 static evchan_t *
102 bind_channel(boolean_t priv, fmev_pri_t pri)
103 {
104 	evchan_t **evcpp = &CHAN_BINDING(priv, pri);
105 	evchan_t *evc;
106 
107 	if (*evcpp != NULL)
108 		return (*evcpp);
109 
110 	if (sysevent_evc_bind(CHAN_NAME(priv, pri), &evc,
111 	    EVCH_CREAT | CHAN_FLAGS(priv, pri)) != 0)
112 		return (NULL);
113 
114 	if (atomic_cas_ptr(evcpp, NULL, evc) != NULL)
115 		(void) sysevent_evc_unbind(evc);
116 
117 	return (*evcpp);
118 }
119 
120 static fmev_err_t
121 vrfy_ruleset(const char *ruleset)
122 {
123 	if (ruleset != NULL &&
124 	    strnlen(ruleset, FMEV_MAX_RULESET_LEN) == FMEV_MAX_RULESET_LEN)
125 		return (FMEVERR_STRING2BIG);
126 
127 	return (FMEV_OK);
128 
129 }
130 
131 static fmev_err_t
132 vrfy_class(const char *class)
133 {
134 	if (class == NULL || *class == '\0')
135 		return (FMEVERR_API);
136 
137 	if (strnlen(class, FMEV_PUB_MAXCLASSLEN) == FMEV_PUB_MAXCLASSLEN)
138 		return (FMEVERR_STRING2BIG);
139 
140 	return (FMEV_OK);
141 }
142 
143 static fmev_err_t
144 vrfy_subclass(const char *subclass)
145 {
146 	if (subclass == NULL || *subclass == '\0')
147 		return (FMEVERR_API);
148 
149 	if (strnlen(subclass, FMEV_PUB_MAXSUBCLASSLEN) ==
150 	    FMEV_PUB_MAXSUBCLASSLEN)
151 		return (FMEVERR_STRING2BIG);
152 
153 	return (FMEV_OK);
154 }
155 
156 static fmev_err_t
157 vrfy_pri(fmev_pri_t pri)
158 {
159 	return (pri == FMEV_LOPRI || pri == FMEV_HIPRI ?
160 	    FMEV_OK : FMEVERR_API);
161 }
162 
163 const char *
164 fmev_pri_string(fmev_pri_t pri)
165 {
166 	static const char *pristr[] = { "low", "high" };
167 
168 	if (vrfy_pri(pri) != FMEV_OK)
169 		return (NULL);
170 
171 	return (pristr[pri - FMEV_LOPRI]);
172 }
173 
174 static fmev_err_t
175 vrfy(const char **rulesetp, const char **classp, const char **subclassp,
176     fmev_pri_t *prip)
177 {
178 	fmev_err_t rc = FMEV_OK;
179 
180 	if (rulesetp && (rc = vrfy_ruleset(*rulesetp)) != FMEV_OK)
181 		return (rc);
182 
183 	if (classp && (rc = vrfy_class(*classp)) != FMEV_OK ||
184 	    subclassp && (rc = vrfy_subclass(*subclassp)) != FMEV_OK ||
185 	    prip && (rc = vrfy_pri(*prip)) != FMEV_OK)
186 		return (rc);
187 
188 	return (FMEV_OK);
189 }
190 
191 uint_t fmev_va2nvl_maxtuples = 100;
192 
193 fmev_err_t
194 va2nvl(nvlist_t **nvlp, va_list ap, uint_t ntuples)
195 {
196 	nvlist_t *nvl = NULL;
197 	uint_t processed = 0;
198 	char *name;
199 
200 	if (ntuples == 0)
201 		return (FMEVERR_INTERNAL);
202 
203 	if ((name = va_arg(ap, char *)) == NULL || name == FMEV_ARG_TERM)
204 		return (FMEVERR_VARARGS_MALFORMED);
205 
206 	if (ntuples > fmev_va2nvl_maxtuples)
207 		return (FMEVERR_VARARGS_TOOLONG);
208 
209 	if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0)
210 		return (FMEVERR_ALLOC);
211 
212 	while (name != NULL && name != FMEV_ARG_TERM && processed <= ntuples) {
213 		data_type_t type;
214 		int err, nelem;
215 
216 		type = va_arg(ap, data_type_t);
217 
218 		switch (type) {
219 		case DATA_TYPE_BYTE:
220 			err = nvlist_add_byte(nvl, name,
221 			    va_arg(ap, uint_t));
222 			break;
223 		case DATA_TYPE_BYTE_ARRAY:
224 			nelem = va_arg(ap, int);
225 			err = nvlist_add_byte_array(nvl, name,
226 			    va_arg(ap, uchar_t *), nelem);
227 			break;
228 		case DATA_TYPE_BOOLEAN_VALUE:
229 			err = nvlist_add_boolean_value(nvl, name,
230 			    va_arg(ap, boolean_t));
231 			break;
232 		case DATA_TYPE_BOOLEAN_ARRAY:
233 			nelem = va_arg(ap, int);
234 			err = nvlist_add_boolean_array(nvl, name,
235 			    va_arg(ap, boolean_t *), nelem);
236 			break;
237 		case DATA_TYPE_INT8:
238 			err = nvlist_add_int8(nvl, name,
239 			    va_arg(ap, int));
240 			break;
241 		case DATA_TYPE_INT8_ARRAY:
242 			nelem = va_arg(ap, int);
243 			err = nvlist_add_int8_array(nvl, name,
244 			    va_arg(ap, int8_t *), nelem);
245 			break;
246 		case DATA_TYPE_UINT8:
247 			err = nvlist_add_uint8(nvl, name,
248 			    va_arg(ap, uint_t));
249 			break;
250 		case DATA_TYPE_UINT8_ARRAY:
251 			nelem = va_arg(ap, int);
252 			err = nvlist_add_uint8_array(nvl, name,
253 			    va_arg(ap, uint8_t *), nelem);
254 			break;
255 		case DATA_TYPE_INT16:
256 			err = nvlist_add_int16(nvl, name,
257 			    va_arg(ap, int));
258 			break;
259 		case DATA_TYPE_INT16_ARRAY:
260 			nelem = va_arg(ap, int);
261 			err = nvlist_add_int16_array(nvl, name,
262 			    va_arg(ap, int16_t *), nelem);
263 			break;
264 		case DATA_TYPE_UINT16:
265 			err = nvlist_add_uint16(nvl, name,
266 			    va_arg(ap, uint_t));
267 			break;
268 		case DATA_TYPE_UINT16_ARRAY:
269 			nelem = va_arg(ap, int);
270 			err = nvlist_add_uint16_array(nvl, name,
271 			    va_arg(ap, uint16_t *), nelem);
272 			break;
273 		case DATA_TYPE_INT32:
274 			err = nvlist_add_int32(nvl, name,
275 			    va_arg(ap, int32_t));
276 			break;
277 		case DATA_TYPE_INT32_ARRAY:
278 			nelem = va_arg(ap, int);
279 			err = nvlist_add_int32_array(nvl, name,
280 			    va_arg(ap, int32_t *), nelem);
281 			break;
282 		case DATA_TYPE_UINT32:
283 			err = nvlist_add_uint32(nvl, name,
284 			    va_arg(ap, uint32_t));
285 			break;
286 		case DATA_TYPE_UINT32_ARRAY:
287 			nelem = va_arg(ap, int);
288 			err = nvlist_add_uint32_array(nvl, name,
289 			    va_arg(ap, uint32_t *), nelem);
290 			break;
291 		case DATA_TYPE_INT64:
292 			err = nvlist_add_int64(nvl, name,
293 			    va_arg(ap, int64_t));
294 			break;
295 		case DATA_TYPE_INT64_ARRAY:
296 			nelem = va_arg(ap, int);
297 			err = nvlist_add_int64_array(nvl, name,
298 			    va_arg(ap, int64_t *), nelem);
299 			break;
300 		case DATA_TYPE_UINT64:
301 			err = nvlist_add_uint64(nvl, name,
302 			    va_arg(ap, uint64_t));
303 			break;
304 		case DATA_TYPE_UINT64_ARRAY:
305 			nelem = va_arg(ap, int);
306 			err = nvlist_add_uint64_array(nvl, name,
307 			    va_arg(ap, uint64_t *), nelem);
308 			break;
309 		case DATA_TYPE_STRING:
310 			err = nvlist_add_string(nvl, name,
311 			    va_arg(ap, char *));
312 			break;
313 		case DATA_TYPE_STRING_ARRAY:
314 			nelem = va_arg(ap, int);
315 			err = nvlist_add_string_array(nvl, name,
316 			    va_arg(ap, char **), nelem);
317 			break;
318 		case DATA_TYPE_NVLIST:
319 			err = nvlist_add_nvlist(nvl, name,
320 			    va_arg(ap, nvlist_t *));
321 			break;
322 		case DATA_TYPE_NVLIST_ARRAY:
323 			nelem = va_arg(ap, int);
324 			err = nvlist_add_nvlist_array(nvl, name,
325 			    va_arg(ap, nvlist_t **), nelem);
326 			break;
327 		case DATA_TYPE_HRTIME:
328 			err = nvlist_add_hrtime(nvl, name,
329 			    va_arg(ap, hrtime_t));
330 			break;
331 		case DATA_TYPE_DOUBLE:
332 			err = nvlist_add_double(nvl, name,
333 			    va_arg(ap, double));
334 			break;
335 		default:
336 			err = EINVAL;
337 		}
338 
339 		if (err)
340 			break;	/* terminate on first error */
341 
342 		processed++;
343 		name = va_arg(ap, char *);
344 	}
345 
346 	if (name != FMEV_ARG_TERM || processed != ntuples) {
347 		*nvlp = NULL;
348 		nvlist_free(nvl);
349 		return (FMEVERR_VARARGS_MALFORMED);
350 	}
351 
352 	*nvlp = nvl;
353 	return (FMEV_SUCCESS);
354 }
355 
356 static fmev_err_t
357 do_publish(const char *file, const char *func, int64_t line,
358     const char *ruleset, const char *class, const char *subclass,
359     fmev_pri_t pri, nvlist_t *nvl, uint_t ntuples, va_list ap)
360 {
361 	fmev_err_t rc = FMEVERR_INTERNAL;
362 	boolean_t priv = B_TRUE;
363 	nvlist_t *tmpnvl = NULL;
364 	nvlist_t *pub;
365 	evchan_t *evc;
366 
367 	if (nvl) {
368 		ASSERT(ntuples == 0);
369 
370 		/*
371 		 * Enforce NV_UNIQUE_NAME
372 		 */
373 		if ((nvlist_nvflag(nvl) & NV_UNIQUE_NAME) != NV_UNIQUE_NAME)
374 			return (FMEVERR_NVLIST);
375 
376 		pub = nvl;
377 
378 	} else if (ntuples != 0) {
379 		fmev_err_t err;
380 
381 		err = va2nvl(&tmpnvl, ap, ntuples);
382 		if (err != FMEV_SUCCESS)
383 			return (err);
384 
385 		pub = tmpnvl;
386 	} else {
387 		/*
388 		 * Even if the caller has no tuples to publish (just an event
389 		 * class and subclass), we are going to add some detector
390 		 * information so we need some nvlist.
391 		 */
392 		if (nvlist_alloc(&tmpnvl, NV_UNIQUE_NAME, 0) != 0)
393 			return (FMEVERR_ALLOC);
394 
395 		pub = tmpnvl;
396 	}
397 
398 	evc = bind_channel(priv, pri);
399 
400 	if (evc == NULL) {
401 		rc = FMEVERR_INTERNAL;
402 		goto done;
403 	}
404 
405 
406 	/*
407 	 * Add detector information
408 	 */
409 	if (file && nvlist_add_string(pub, "__fmev_file", file) != 0 ||
410 	    func && nvlist_add_string(pub, "__fmev_func", func) != 0 ||
411 	    line != -1 && nvlist_add_int64(pub, "__fmev_line", line) != 0 ||
412 	    nvlist_add_int32(pub, "__fmev_pid", getpid()) != 0 ||
413 	    nvlist_add_string(pub, "__fmev_execname", getexecname()) != 0) {
414 		rc = FMEVERR_ALLOC;
415 		goto done;
416 	}
417 
418 	if (ruleset == NULL)
419 		ruleset = FMEV_RULESET_DEFAULT;
420 
421 	/*
422 	 * We abuse the GPEC publication arguments as follows:
423 	 *
424 	 * GPEC argument	Our usage
425 	 * -------------------- -----------------
426 	 * const char *class	Raw class
427 	 * const char *subclass	Raw subclass
428 	 * const char *vendor	Ruleset name
429 	 * const char *pub_name	Unused
430 	 * nvlist_t *attr_list	Event attributes
431 	 */
432 	rc = (sysevent_evc_publish(evc, class, subclass, ruleset, "",
433 	    pub, EVCH_NOSLEEP) == 0) ? FMEV_SUCCESS : FMEVERR_TRANSPORT;
434 
435 done:
436 	/* Free a passed in nvlist iff success */
437 	if (nvl && rc == FMEV_SUCCESS)
438 		nvlist_free(nvl);
439 
440 	if (tmpnvl)
441 		nvlist_free(tmpnvl);
442 
443 	return (rc);
444 }
445 
446 fmev_err_t
447 _i_fmev_publish_nvl(
448     const char *file, const char *func, int64_t line,
449     const char *ruleset, const char *class, const char *subclass,
450     fmev_pri_t pri, nvlist_t *attr)
451 {
452 	fmev_err_t rc;
453 
454 	if ((rc = vrfy(&ruleset, &class, &subclass, &pri)) != FMEV_OK)
455 		return (rc);		/* any attr not freed */
456 
457 	return (do_publish(file, func, line,
458 	    ruleset, class, subclass,
459 	    pri, attr, 0, NULL));	/* any attr freed iff success */
460 }
461 
462 fmev_err_t
463 _i_fmev_publish(
464     const char *file, const char *func, int64_t line,
465     const char *ruleset, const char *class, const char *subclass,
466     fmev_pri_t pri,
467     uint_t ntuples, ...)
468 {
469 	va_list ap;
470 	fmev_err_t rc;
471 
472 	if ((rc = vrfy(&ruleset, &class, &subclass, &pri)) != FMEV_OK)
473 		return (rc);
474 
475 	if (ntuples != 0)
476 		va_start(ap, ntuples);
477 
478 	rc = do_publish(file, func, line,
479 	    ruleset, class, subclass,
480 	    pri, NULL, ntuples, ap);
481 
482 	if (ntuples != 0)
483 		va_end(ap);
484 
485 	return (rc);
486 }
487 
488 
489 #pragma	weak fmev_publish = _fmev_publish
490 #pragma	weak fmev_rspublish = _fmev_rspublish
491 
492 static fmev_err_t
493 _fmev_publish(const char *class, const char *subclass, fmev_pri_t pri,
494     uint_t ntuples, ...)
495 {
496 	fmev_err_t rc;
497 	va_list ap;
498 
499 	if ((rc = vrfy(NULL, &class, &subclass, &pri)) != FMEV_OK)
500 		return (rc);
501 
502 	if (ntuples != 0)
503 		va_start(ap, ntuples);
504 
505 	rc = do_publish(NULL, NULL, -1,
506 	    FMEV_RULESET_DEFAULT, class, subclass,
507 	    pri, NULL, ntuples, ap);
508 
509 	if (ntuples != 0)
510 		va_end(ap);
511 
512 	return (rc);
513 }
514 
515 static fmev_err_t
516 _fmev_rspublish(const char *ruleset, const char *class, const char *subclass,
517     fmev_pri_t pri, uint_t ntuples, ...)
518 {
519 	fmev_err_t rc;
520 	va_list ap;
521 
522 	if ((rc = vrfy(&ruleset, &class, &subclass, &pri)) != FMEV_OK)
523 		return (rc);
524 
525 	if (ntuples != 0)
526 		va_start(ap, ntuples);
527 
528 	rc = do_publish(NULL, NULL, -1,
529 	    ruleset, class, subclass,
530 	    pri, NULL, ntuples, ap);
531 
532 	if (ntuples != 0)
533 		va_end(ap);
534 
535 	return (rc);
536 }
537