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.
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.
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]
23 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
27 * Receive (on GPEC channels) raw events published by a few select producers
28 * using the private libfmevent publication interfaces, and massage those
29 * raw events into full protocol events. Each raw event selects a "ruleset"
30 * by which to perform the transformation into a protocol event.
32 * Only publication from userland running privileged is supported; two
33 * channels are used - one for high-value and one for low-value events.
34 * There is some planning in the implementation below for kernel hi and low
35 * value channels, and for non-privileged userland low and hi value channels.
38 #include <fm/fmd_api.h>
39 #include <fm/libfmevent.h>
40 #include <uuid/uuid.h>
41 #include <libsysevent.h>
43 #include <libnvpair.h>
49 static struct fmevt_inbound_stats
{
50 fmd_stat_t raw_callbacks
;
51 fmd_stat_t raw_noattrlist
;
52 fmd_stat_t raw_nodetector
;
53 fmd_stat_t pp_bad_ruleset
;
54 fmd_stat_t pp_explicitdrop
;
55 fmd_stat_t pp_fallthrurule
;
56 fmd_stat_t pp_fanoutmax
;
57 fmd_stat_t pp_intldrop
;
58 fmd_stat_t pp_badclass
;
59 fmd_stat_t pp_nvlallocfail
;
60 fmd_stat_t pp_nvlbuildfail
;
61 fmd_stat_t pp_badreturn
;
62 fmd_stat_t xprt_posted
;
64 { "raw_callbacks", FMD_TYPE_UINT64
,
65 "total raw event callbacks from producers" },
66 { "raw_noattrlist", FMD_TYPE_UINT64
,
67 "missing attribute list" },
68 { "raw_nodetector", FMD_TYPE_UINT64
,
69 "unable to add detector" },
70 { "pp_bad_ruleset", FMD_TYPE_UINT64
,
71 "post-process bad ruleset" },
72 { "pp_explicitdrop", FMD_TYPE_UINT64
,
73 "ruleset drops event with NULL func" },
74 { "pp_fanoutmax", FMD_TYPE_UINT64
,
75 "post-processing produced too many events" },
76 { "pp_intldrop", FMD_TYPE_UINT64
,
77 "post-processing requested event drop" },
78 { "pp_badclass", FMD_TYPE_UINT64
,
79 "post-processing produced invalid event class" },
80 { "pp_nvlallocfail", FMD_TYPE_UINT64
,
81 "fmd_nvl_alloc failed" },
82 { "pp_nvlbuildfail", FMD_TYPE_UINT64
,
83 "nvlist_add_foo failed in building event" },
84 { "pp_badreturn", FMD_TYPE_UINT64
,
85 "inconsistent number of events returned" },
86 { "xprt_posted", FMD_TYPE_UINT64
,
87 "protocol events posted with fmd_xprt_post" },
90 static int isglobalzone
;
91 static char zonename
[ZONENAME_MAX
];
93 #define BUMPSTAT(stat) inbound_stats.stat.fmds_value.ui64++
99 #define CBF_ALL (CBF_USER | CBF_PRIV | CBF_LV | CBF_HV)
101 static struct fmevt_chaninfo
{
102 const char *ci_propname
; /* property to get channel name */
103 evchan_t
*ci_binding
; /* GPEC binding for this channel */
104 char ci_sid
[MAX_SUBID_LEN
]; /* subscriber id */
105 uint32_t ci_cbarg
; /* callback cookie */
106 uint32_t ci_sflags
; /* subscription flags to use */
108 { "user_priv_highval_channel", NULL
, { 0 },
109 CBF_USER
| CBF_PRIV
| CBF_HV
, EVCH_SUB_KEEP
},
110 { "user_priv_lowval_channel", NULL
, { 0 },
111 CBF_USER
| CBF_PRIV
| CBF_LV
, EVCH_SUB_KEEP
},
114 static pthread_cond_t fmevt_cv
= PTHREAD_COND_INITIALIZER
;
115 static pthread_mutex_t fmevt_lock
= PTHREAD_MUTEX_INITIALIZER
;
116 static int fmevt_exiting
;
118 static fmd_xprt_t
*fmevt_xprt
;
119 static uint32_t fmevt_xprt_refcnt
;
120 static sysevent_subattr_t
*subattr
;
123 * Rulesets we recognize and who handles them. Additions and changes
124 * must follow the Portfolio Review process. At ths time only
125 * the FMEV_RULESET_ON_SUNOS and FMEVT_RULESET_SMF rulesets are
126 * formally recognized by that process - the others here are experimental.
128 static struct fmevt_rs
{
130 fmevt_pp_func_t
*rs_ppfunc
;
134 { FMEV_RULESET_SMF
, fmevt_pp_smf
},
135 { FMEV_RULESET_ON_EREPORT
, fmevt_pp_on_ereport
},
136 { FMEV_RULESET_ON_SUNOS
, fmevt_pp_on_sunos
},
137 { FMEV_RULESET_ON_PRIVATE
, fmevt_pp_on_private
},
138 { FMEV_RULESET_UNREGISTERED
, fmevt_pp_unregistered
}
142 * Take a ruleset specification string and separate it into namespace
143 * and subsystem components.
146 fmevt_rs_burst(fmd_hdl_t
*hdl
, char *ruleset
, char **nsp
, char **subsysp
,
152 if (ruleset
== NULL
|| *ruleset
== '\0' ||
153 strnlen(ruleset
, FMEV_MAX_RULESET_LEN
) == FMEV_MAX_RULESET_LEN
)
156 if (alloc
== B_FALSE
) {
158 ns
= strsep(&s
, FMEV_RS_SEPARATOR
);
160 if (s
== NULL
|| s
== ns
+ 1)
163 if ((s
= strstr(ruleset
, FMEV_RS_SEPARATOR
)) == NULL
||
164 s
== ruleset
+ strlen(ruleset
) - 1)
169 ns
= fmd_hdl_alloc(hdl
, len
+ 1, FMD_SLEEP
);
170 (void) strncpy(ns
, ruleset
, len
);
177 *nsp
= ns
; /* caller must free if alloc == B_TRUE */
180 *subsysp
= s
; /* always within original ruleset string */
186 fmevt_rs_init(fmd_hdl_t
*hdl
)
190 for (i
= 0; i
< sizeof (rulelist
) / sizeof (rulelist
[0]); i
++) {
191 struct fmevt_rs
*rsp
= &rulelist
[i
];
193 if (!fmevt_rs_burst(hdl
, rsp
->rs_pat
, &rsp
->rs_namespace
,
194 &rsp
->rs_subsys
, B_TRUE
))
202 * Construct a "sw" scheme detector FMRI.
204 * We make no use of priv or pri.
208 fmevt_detector(nvlist_t
*attr
, char *ruleset
, int user
, int priv
,
211 char buf
[FMEV_MAX_RULESET_LEN
+ 1];
213 nvlist_t
*obj
, *dtcr
, *site
, *ctxt
;
214 char *execname
= NULL
;
220 (void) strncpy(buf
, ruleset
, sizeof (buf
));
221 if (!fmevt_rs_burst(NULL
, buf
, &ns
, &subsys
, B_FALSE
))
224 obj
= fmd_nvl_alloc(fmevt_hdl
, FMD_SLEEP
);
225 dtcr
= fmd_nvl_alloc(fmevt_hdl
, FMD_SLEEP
);
226 site
= fmd_nvl_alloc(fmevt_hdl
, FMD_SLEEP
);
227 ctxt
= fmd_nvl_alloc(fmevt_hdl
, FMD_SLEEP
);
229 if (obj
== NULL
|| dtcr
== NULL
|| site
== NULL
|| ctxt
== NULL
) {
235 * Build up 'object' nvlist.
237 if (nvlist_lookup_string(attr
, "__fmev_execname", &execname
) == 0)
238 err
+= nvlist_add_string(obj
, FM_FMRI_SW_OBJ_PATH
, execname
);
241 * Build up 'site' nvlist. We should have source file and line
242 * number and, if the producer was compiled with C99, function name.
244 if (nvlist_lookup_string(attr
, "__fmev_file", &str
) == 0) {
245 err
+= nvlist_add_string(site
, FM_FMRI_SW_SITE_FILE
, str
);
246 (void) nvlist_remove(attr
, "__fmev_file", DATA_TYPE_STRING
);
249 if (nvlist_lookup_string(attr
, "__fmev_func", &str
) == 0) {
250 err
+= nvlist_add_string(site
, FM_FMRI_SW_SITE_FUNC
, str
);
251 (void) nvlist_remove(attr
, "__fmev_func", DATA_TYPE_STRING
);
254 if (nvlist_lookup_int64(attr
, "__fmev_line", &i64
) == 0) {
255 err
+= nvlist_add_int64(site
, FM_FMRI_SW_SITE_LINE
, i64
);
256 (void) nvlist_remove(attr
, "__fmev_line", DATA_TYPE_INT64
);
260 * Build up 'context' nvlist. We do not include contract id at
264 err
+= nvlist_add_string(ctxt
, FM_FMRI_SW_CTXT_ORIGIN
,
265 user
? "userland" : "kernel");
268 err
+= nvlist_add_string(ctxt
, FM_FMRI_SW_CTXT_EXECNAME
,
270 (void) nvlist_remove(attr
, "__fmev_execname", DATA_TYPE_STRING
);
273 if (nvlist_lookup_int32(attr
, "__fmev_pid", &i32
) == 0) {
274 err
+= nvlist_add_int32(ctxt
, FM_FMRI_SW_CTXT_PID
, i32
);
275 (void) nvlist_remove(attr
, "__fmev_pid", DATA_TYPE_INT32
);
279 err
+= nvlist_add_string(ctxt
, FM_FMRI_SW_CTXT_ZONE
, zonename
);
281 /* Put it all together */
283 err
+= nvlist_add_uint8(dtcr
, FM_VERSION
, SW_SCHEME_VERSION0
);
284 err
+= nvlist_add_string(dtcr
, FM_FMRI_SCHEME
, FM_FMRI_SCHEME_SW
);
285 err
+= nvlist_add_nvlist(dtcr
, FM_FMRI_SW_OBJ
, obj
);
286 err
+= nvlist_add_nvlist(dtcr
, FM_FMRI_SW_SITE
, site
);
287 err
+= nvlist_add_nvlist(dtcr
, FM_FMRI_SW_CTXT
, ctxt
);
303 class_ok(char *class)
305 static const char *approved
[] = {
306 FM_IREPORT_CLASS
".",
312 for (i
= 0; i
< sizeof (approved
) / sizeof (approved
[0]); i
++) {
313 if (strncmp(class, approved
[i
], strlen(approved
[i
])) == 0)
321 fmevt_postprocess(char *ruleset
, nvlist_t
*dtcr
, nvlist_t
*rawattr
,
322 struct fmevt_ppargs
*eap
)
324 uint_t expected
= 0, processed
= 0;
325 char rs2burst
[FMEV_MAX_RULESET_LEN
+ 1];
326 char *class[FMEVT_FANOUT_MAX
];
327 nvlist_t
*attr
[FMEVT_FANOUT_MAX
];
328 fmevt_pp_func_t
*dispf
= NULL
;
329 char buf
[FMEV_MAX_CLASS
];
334 (void) strncpy(rs2burst
, ruleset
, sizeof (rs2burst
));
335 if (!fmevt_rs_burst(NULL
, rs2burst
, &ns
, &subsys
, B_FALSE
)) {
336 BUMPSTAT(pp_bad_ruleset
);
341 * Lookup a matching rule in our table.
343 for (i
= 0; i
< sizeof (rulelist
) / sizeof (rulelist
[0]); i
++) {
344 struct fmevt_rs
*rsp
= &rulelist
[i
];
346 if (*ns
!= '*' && *rsp
->rs_namespace
!= '*' &&
347 strcmp(ns
, rsp
->rs_namespace
) != 0)
350 if (*subsys
!= '*' && *rsp
->rs_subsys
!= '*' &&
351 strcmp(subsys
, rsp
->rs_subsys
) != 0)
354 dispf
= rsp
->rs_ppfunc
;
361 * If a ruleset matches but specifies a NULL function then
362 * it's electing to drop the event. If no rule was matched
363 * then default to unregistered processing.
367 BUMPSTAT(pp_explicitdrop
);
370 BUMPSTAT(pp_fallthrurule
);
371 dispf
= fmevt_pp_unregistered
;
376 * Clear the arrays in which class strings and attribute
377 * nvlists can be returned. Pass a pointer to our stack buffer
378 * that the callee can use for the first event class (for others
379 * it must fmd_hdl_alloc and we'll free below). We will free
380 * and nvlists that are returned.
382 bzero(class, sizeof (class));
383 bzero(attr
, sizeof (attr
));
387 * Generate an event UUID which will be used for the first
388 * event generated by post-processing; if post-processing
389 * fans out into more than one event the additional events
390 * can reference this uuid (but we don't generate their
391 * UUIDs until later).
394 uuid_unparse(uu
, eap
->pp_uuidstr
);
397 * Call selected post-processing function. See block comment
398 * in fmevt.h for a description of this process.
400 expected
= (*dispf
)(class, attr
, ruleset
,
401 (const nvlist_t
*)dtcr
, rawattr
,
402 (const struct fmevt_ppargs
*)eap
);
404 if (expected
> FMEVT_FANOUT_MAX
) {
405 BUMPSTAT(pp_fanoutmax
);
406 return; /* without freeing class and nvl - could leak */
407 } else if (expected
== 0) {
408 BUMPSTAT(pp_intldrop
);
413 * Post as many events as the callback completed.
415 for (i
= 0; i
< FMEVT_FANOUT_MAX
; i
++) {
416 char uuidstr
[36 + 1];
421 if (class[i
] == NULL
)
424 if (!class_ok(class[i
])) {
425 BUMPSTAT(pp_badclass
);
429 if (processed
++ == 0) {
430 uuidstrp
= eap
->pp_uuidstr
;
433 uuid_unparse(uu
, uuidstr
);
437 if ((nvl
= fmd_nvl_alloc(fmevt_hdl
, FMD_SLEEP
)) == NULL
) {
438 BUMPSTAT(pp_nvlallocfail
);
442 err
+= nvlist_add_uint8(nvl
, FM_VERSION
, 0);
443 err
+= nvlist_add_string(nvl
, FM_CLASS
, (const char *)class[i
]);
444 err
+= nvlist_add_string(nvl
, FM_IREPORT_UUID
, uuidstrp
);
445 err
+= nvlist_add_nvlist(nvl
, FM_IREPORT_DETECTOR
, dtcr
);
446 err
+= nvlist_add_string(nvl
, FM_IREPORT_PRIORITY
,
447 fmev_pri_string(eap
->pp_pri
) ?
448 fmev_pri_string(eap
->pp_pri
) : "?");
451 err
+= nvlist_add_nvlist(nvl
, FM_IREPORT_ATTRIBUTES
,
455 * If we post the event into fmd_xport_post then the
456 * transport code is responsible for freeing the nvl we
460 fmd_xprt_post(fmevt_hdl
, fmevt_xprt
, nvl
,
463 BUMPSTAT(pp_nvlbuildfail
);
468 if (processed
!= expected
)
469 BUMPSTAT(pp_badreturn
);
471 for (i
= 0; i
< FMEVT_FANOUT_MAX
; i
++) {
473 * We provided storage for class[0] but any
474 * additional events have allocated a string.
476 if (i
> 0 && class[i
] != NULL
)
477 fmd_hdl_strfree(fmevt_hdl
, class[i
]);
480 * Free all attribute lists passed in if they are not
481 * just a pointer to the raw attributes
483 if (attr
[i
] != NULL
&& attr
[i
] != rawattr
)
484 nvlist_free(attr
[i
]);
489 fmevt_cb(sysevent_t
*sep
, void *arg
)
491 char *ruleset
= NULL
, *rawclass
, *rawsubclass
;
492 uint32_t cbarg
= (uintptr_t)arg
;
493 nvlist_t
*rawattr
= NULL
;
494 struct fmevt_ppargs ea
;
499 BUMPSTAT(raw_callbacks
);
501 if (cbarg
& ~CBF_ALL
)
502 fmd_hdl_abort(fmevt_hdl
, "event receipt callback with "
505 user
= (cbarg
& CBF_USER
) != 0;
506 priv
= (cbarg
& CBF_PRIV
) != 0;
507 pri
= (cbarg
& CBF_HV
? FMEV_HIPRI
: FMEV_LOPRI
);
509 (void) pthread_mutex_lock(&fmevt_lock
);
512 while (fmevt_xprt_refcnt
> 0)
513 (void) pthread_cond_wait(&fmevt_cv
, &fmevt_lock
);
514 (void) pthread_mutex_unlock(&fmevt_lock
);
515 return (0); /* discard event */
519 (void) pthread_mutex_unlock(&fmevt_lock
);
521 ruleset
= sysevent_get_vendor_name(sep
); /* must free */
522 rawclass
= sysevent_get_class_name(sep
); /* valid with sep */
523 rawsubclass
= sysevent_get_subclass_name(sep
); /* valid with sep */
525 if (sysevent_get_attr_list(sep
, &rawattr
) != 0) {
526 BUMPSTAT(raw_noattrlist
);
530 if ((dtcr
= fmevt_detector(rawattr
, ruleset
, user
, priv
,
532 BUMPSTAT(raw_nodetector
);
536 ea
.pp_rawclass
= rawclass
;
537 ea
.pp_rawsubclass
= rawsubclass
;
538 sysevent_get_time(sep
, &ea
.pp_hrt
);
543 fmevt_postprocess(ruleset
, dtcr
, rawattr
, &ea
);
546 (void) pthread_mutex_lock(&fmevt_lock
);
548 if (--fmevt_xprt_refcnt
== 0 && fmevt_exiting
)
549 (void) pthread_cond_broadcast(&fmevt_cv
);
551 (void) pthread_mutex_unlock(&fmevt_lock
);
555 nvlist_free(rawattr
);
557 return (0); /* in all cases consider the event delivered */
561 fmevt_init_inbound(fmd_hdl_t
*hdl
)
567 if (!fmevt_rs_init(hdl
))
568 fmd_hdl_abort(hdl
, "error in fmevt_rs_init\n");
570 (void) fmd_stat_create(hdl
, FMD_STAT_NOALLOC
, sizeof (inbound_stats
) /
571 sizeof (fmd_stat_t
), (fmd_stat_t
*)&inbound_stats
);
573 zoneid
= getzoneid();
574 isglobalzone
= (zoneid
== GLOBAL_ZONEID
);
575 if (getzonenamebyid(zoneid
, zonename
, sizeof (zonename
)) == -1)
576 fmd_hdl_abort(hdl
, "getzonenamebyid failed");
578 if ((subattr
= sysevent_subattr_alloc()) == NULL
)
579 fmd_hdl_abort(hdl
, "failed to allocate subscription "
582 sysevent_subattr_thrcreate(subattr
, fmd_doorthr_create
, NULL
);
583 sysevent_subattr_thrsetup(subattr
, fmd_doorthr_setup
, NULL
);
585 sidpfx
= fmd_prop_get_string(hdl
, "sidprefix");
586 fmevt_xprt
= fmd_xprt_open(hdl
, FMD_XPRT_RDONLY
, NULL
, NULL
);
588 for (i
= 0; i
< sizeof (chaninfo
) / sizeof (chaninfo
[0]); i
++) {
589 struct fmevt_chaninfo
*cip
= &chaninfo
[i
];
590 char *channel
= fmd_prop_get_string(hdl
, cip
->ci_propname
);
593 if (sysevent_evc_bind(channel
, &cip
->ci_binding
,
594 EVCH_CREAT
| EVCH_HOLD_PEND_INDEF
) != 0)
595 fmd_hdl_abort(hdl
, "failed to bind GPEC channel for "
596 "channel %s", channel
);
598 (void) snprintf(cip
->ci_sid
, sizeof (cip
->ci_sid
),
600 cip
->ci_cbarg
& CBF_USER
? 'u' : 'k',
601 cip
->ci_cbarg
& CBF_PRIV
? 'p' : 'n',
602 cip
->ci_cbarg
& CBF_HV
? 'h' : 'l');
604 err
= sysevent_evc_xsubscribe(cip
->ci_binding
, cip
->ci_sid
,
605 EC_ALL
, fmevt_cb
, (void *)(uintptr_t)cip
->ci_cbarg
,
606 cip
->ci_sflags
, subattr
);
609 fmd_hdl_abort(hdl
, "another fmd is active on "
610 "channel %s\n", channel
);
612 fmd_hdl_abort(hdl
, "failed to subscribe to channel %s",
615 fmd_prop_free_string(hdl
, channel
);
618 fmd_prop_free_string(hdl
, sidpfx
);
622 fmevt_fini_inbound(fmd_hdl_t
*hdl
)
626 for (i
= 0; i
< sizeof (chaninfo
) / sizeof (chaninfo
[0]); i
++) {
627 struct fmevt_chaninfo
*cip
= &chaninfo
[i
];
629 if (cip
->ci_binding
) {
630 (void) sysevent_evc_unsubscribe(cip
->ci_binding
,
632 (void) sysevent_evc_unbind(cip
->ci_binding
);
633 cip
->ci_binding
= NULL
;
638 sysevent_subattr_free(subattr
);
643 /* drain before destruction */
644 (void) pthread_mutex_lock(&fmevt_lock
);
646 while (fmevt_xprt_refcnt
> 0)
647 (void) pthread_cond_wait(&fmevt_cv
, &fmevt_lock
);
648 (void) pthread_mutex_unlock(&fmevt_lock
);
650 fmd_xprt_close(hdl
, fmevt_xprt
);