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
62static 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 */
84static void
85clear_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
95static void
96_fmev_publish_init(void)
97{
98	(void) pthread_atfork(NULL, NULL, clear_bindings);
99}
100
101static evchan_t *
102bind_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
120static fmev_err_t
121vrfy_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
131static fmev_err_t
132vrfy_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
143static fmev_err_t
144vrfy_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
156static fmev_err_t
157vrfy_pri(fmev_pri_t pri)
158{
159	return (pri == FMEV_LOPRI || pri == FMEV_HIPRI ?
160	    FMEV_OK : FMEVERR_API);
161}
162
163const char *
164fmev_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
174static fmev_err_t
175vrfy(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
191uint_t fmev_va2nvl_maxtuples = 100;
192
193fmev_err_t
194va2nvl(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
356static fmev_err_t
357do_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
435done:
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
446fmev_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
462fmev_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
492static 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
515static 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