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 * Copyright 2018 Nexenta Systems, Inc.
28 */
29
30 #include <fm/libtopo.h>
31
32 #include <alloca.h>
33
34 #include "libfmnotify.h"
35
36 /*ARGSUSED*/
37 void
38 nd_cleanup(nd_hdl_t *nhdl)
39 {
40 nd_debug(nhdl, "Cleaning up ...");
41 if (nhdl->nh_evhdl)
42 (void) fmev_shdl_fini(nhdl->nh_evhdl);
43
44 if (nhdl->nh_msghdl)
45 fmd_msg_fini(nhdl->nh_msghdl);
46
47 nhdl->nh_keep_running = B_FALSE;
48 (void) fclose(nhdl->nh_log_fd);
49 }
50
51 static void
52 get_timestamp(char *buf, size_t bufsize)
53 {
54 time_t utc_time;
55 struct tm *p_tm;
56
57 (void) time(&utc_time);
58 p_tm = localtime(&utc_time);
59
60 (void) strftime(buf, bufsize, "%b %d %H:%M:%S", p_tm);
61 }
62
63 /* PRINTFLIKE2 */
64 void
65 nd_debug(nd_hdl_t *nhdl, const char *format, ...)
66 {
67 char timestamp[64];
68 va_list ap;
69
70 if (nhdl->nh_debug) {
71 get_timestamp(timestamp, sizeof (timestamp));
72 (void) fprintf(nhdl->nh_log_fd, "[ %s ", timestamp);
73 va_start(ap, format);
74 (void) vfprintf(nhdl->nh_log_fd, format, ap);
75 va_end(ap);
76 (void) fprintf(nhdl->nh_log_fd, " ]\n");
77 }
78 (void) fflush(nhdl->nh_log_fd);
79 }
80
81 void
82 nd_dump_nvlist(nd_hdl_t *nhdl, nvlist_t *nvl)
83 {
84 if (nhdl->nh_debug)
85 nvlist_print(nhdl->nh_log_fd, nvl);
86 }
87
88 /* PRINTFLIKE2 */
89 void
90 nd_error(nd_hdl_t *nhdl, const char *format, ...)
91 {
92 char timestamp[64];
93 va_list ap;
94
95 get_timestamp(timestamp, sizeof (timestamp));
96 (void) fprintf(nhdl->nh_log_fd, "[ %s ", timestamp);
97 va_start(ap, format);
98 (void) vfprintf(nhdl->nh_log_fd, format, ap);
99 va_end(ap);
100 (void) fprintf(nhdl->nh_log_fd, " ]\n");
101 (void) fflush(nhdl->nh_log_fd);
102 }
103
104 /* PRINTFLIKE2 */
105 void
106 nd_abort(nd_hdl_t *nhdl, const char *format, ...)
107 {
108 char timestamp[64];
109 va_list ap;
110
111 get_timestamp(timestamp, sizeof (timestamp));
112 (void) fprintf(nhdl->nh_log_fd, "[ %s ", timestamp);
113 va_start(ap, format);
114 (void) vfprintf(nhdl->nh_log_fd, format, ap);
115 va_end(ap);
116 (void) fprintf(nhdl->nh_log_fd, " ]\n");
117 (void) fflush(nhdl->nh_log_fd);
118 nd_cleanup(nhdl);
119 }
120
121 void
122 nd_daemonize(nd_hdl_t *nhdl)
123 {
124 pid_t pid;
125
126 if ((pid = fork()) < 0)
127 nd_abort(nhdl, "Failed to fork child (%s)", strerror(errno));
128 else if (pid > 0)
129 exit(0);
130
131 (void) setsid();
132 (void) close(0);
133 (void) close(1);
134 /*
135 * We leave stderr open so we can write debug/err messages to the SMF
136 * service log
137 */
138 nhdl->nh_is_daemon = B_TRUE;
139 }
140
141 /*
142 * This function returns a pointer to the specified SMF property group for the
143 * specified SMF service. The caller is responsible for freeing the property
144 * group. On failure, the function returns NULL.
145 */
146 static scf_propertygroup_t *
147 nd_get_pg(nd_hdl_t *nhdl, scf_handle_t *handle, const char *svcname,
148 const char *pgname)
149 {
150 scf_scope_t *sc = NULL;
151 scf_service_t *svc = NULL;
152 scf_propertygroup_t *pg = NULL, *ret = NULL;
153
154 sc = scf_scope_create(handle);
155 svc = scf_service_create(handle);
156 pg = scf_pg_create(handle);
157
158 if (sc == NULL || svc == NULL || pg == NULL) {
159 nd_error(nhdl, "Failed to allocate libscf structures");
160 scf_pg_destroy(pg);
161 goto get_pg_done;
162 }
163
164 if (scf_handle_bind(handle) != -1 &&
165 scf_handle_get_scope(handle, SCF_SCOPE_LOCAL, sc) != -1 &&
166 scf_scope_get_service(sc, svcname, svc) != -1 &&
167 scf_service_get_pg(svc, pgname, pg) != -1)
168 ret = pg;
169 else
170 scf_pg_destroy(pg);
171
172 get_pg_done:
173 scf_service_destroy(svc);
174 scf_scope_destroy(sc);
175
176 return (ret);
177 }
178
179 int
180 nd_get_astring_prop(nd_hdl_t *nhdl, const char *svcname, const char *pgname,
181 const char *propname, char **val)
182 {
183 scf_handle_t *handle = NULL;
184 scf_propertygroup_t *pg;
185 scf_property_t *prop = NULL;
186 scf_value_t *value = NULL;
187 char strval[255];
188 int ret = -1;
189
190 if ((handle = scf_handle_create(SCF_VERSION)) == NULL)
191 return (ret);
192
193 if ((pg = nd_get_pg(nhdl, handle, svcname, pgname)) == NULL) {
194 nd_error(nhdl, "Failed to read retrieve %s "
195 "property group for %s", pgname, svcname);
196 goto astring_done;
197 }
198 prop = scf_property_create(handle);
199 value = scf_value_create(handle);
200 if (prop == NULL || value == NULL) {
201 nd_error(nhdl, "Failed to allocate SMF structures");
202 goto astring_done;
203 }
204 if (scf_pg_get_property(pg, propname, prop) == -1 ||
205 scf_property_get_value(prop, value) == -1 ||
206 scf_value_get_astring(value, strval, 255) == -1) {
207 nd_error(nhdl, "Failed to retrieve %s prop (%s)", propname,
208 scf_strerror(scf_error()));
209 goto astring_done;
210 }
211 *val = strdup(strval);
212 ret = 0;
213
214 astring_done:
215 scf_value_destroy(value);
216 scf_property_destroy(prop);
217 scf_pg_destroy(pg);
218 scf_handle_destroy(handle);
219
220 return (ret);
221 }
222
223 int
224 nd_get_boolean_prop(nd_hdl_t *nhdl, const char *svcname, const char *pgname,
225 const char *propname, uint8_t *val)
226 {
227 scf_handle_t *handle = NULL;
228 scf_propertygroup_t *pg;
229 scf_property_t *prop = NULL;
230 scf_value_t *value = NULL;
231 int ret = -1;
232
233 if ((handle = scf_handle_create(SCF_VERSION)) == NULL)
234 return (ret);
235
236 if ((pg = nd_get_pg(nhdl, handle, svcname, pgname)) == NULL) {
237 nd_error(nhdl, "Failed to read retrieve %s "
238 "property group for %s", pgname, svcname);
239 goto bool_done;
240 }
241 prop = scf_property_create(handle);
242 value = scf_value_create(handle);
243 if (prop == NULL || value == NULL) {
244 nd_error(nhdl, "Failed to allocate SMF structures");
245 goto bool_done;
246 }
247 if (scf_pg_get_property(pg, propname, prop) == -1 ||
248 scf_property_get_value(prop, value) == -1 ||
249 scf_value_get_boolean(value, val) == -1) {
250 nd_error(nhdl, "Failed to retrieve %s prop (%s)", propname,
251 scf_strerror(scf_error()));
252 goto bool_done;
253 }
254 ret = 0;
255
256 bool_done:
257 scf_value_destroy(value);
258 scf_property_destroy(prop);
259 scf_pg_destroy(pg);
260 scf_handle_destroy(handle);
261
262 return (ret);
263 }
264
265 char *
266 nd_get_event_fmri(nd_hdl_t *nhdl, fmev_t ev)
267 {
268 nvlist_t *ev_nvl, *snvl;
269 nvlist_t **fnvl;
270 uint_t nnvl;
271 char *svcname;
272
273 if ((ev_nvl = fmev_attr_list(ev)) == NULL) {
274 nd_error(nhdl, "failed to lookup event nvlist");
275 return (NULL);
276 }
277
278 /* If this is an ireport, simply lookup svc-string */
279 if (nvlist_lookup_nvlist(ev_nvl, "attr", &snvl) == 0 &&
280 nvlist_lookup_string(snvl, "svc-string", &svcname) == 0)
281 return (strdup((const char *)svcname));
282
283 /* Otherwise extract the fault-list and use the first element */
284 if (nvlist_lookup_nvlist_array(ev_nvl, FM_SUSPECT_FAULT_LIST,
285 &fnvl, &nnvl) != 0 || nnvl != 1)
286 return (NULL);
287
288 /*
289 * NOTE: this should match the logic in libfmd_snmp.
290 *
291 * Use the following order of nested nvlists to make up FMRI:
292 * - FRU
293 * - ASRU
294 * - resource
295 */
296 if (nvlist_lookup_nvlist(fnvl[0], FM_FAULT_FRU, &snvl) == 0 ||
297 nvlist_lookup_nvlist(fnvl[0], FM_FAULT_ASRU, &snvl) == 0 ||
298 nvlist_lookup_nvlist(fnvl[0], FM_FAULT_RESOURCE, &snvl) == 0) {
299 topo_hdl_t *thp;
300 int topoerr;
301 char *fmri, *ret = NULL;
302
303 thp = topo_open(TOPO_VERSION, NULL, &topoerr);
304 if (thp == NULL)
305 return (NULL);
306
307 if (topo_fmri_nvl2str(thp, snvl, &fmri, &topoerr) == 0) {
308 ret = strdup(fmri);
309 topo_hdl_strfree(thp, fmri);
310 }
311
312 topo_close(thp);
313 return (ret);
314 }
315
316 return (NULL);
317 }
318
319 int
320 nd_get_notify_prefs(nd_hdl_t *nhdl, const char *mech, fmev_t ev,
321 nvlist_t ***pref_nvl, uint_t *nprefs)
322 {
323 nvlist_t *ev_nvl, *top_nvl, **np_nvlarr, *mech_nvl;
324 int ret = 1;
325 uint_t nelem;
326
327 if ((ev_nvl = fmev_attr_list(ev)) == NULL) {
328 nd_error(nhdl, "Failed to lookup event attr nvlist");
329 return (-1);
330 }
331
332 if ((ret = smf_notify_get_params(&top_nvl, ev_nvl)) != SCF_SUCCESS) {
333 ret = scf_error();
334 if (ret == SCF_ERROR_NOT_FOUND) {
335 nd_debug(nhdl, "No notification preferences specified "
336 "for this event");
337 goto pref_done;
338 } else {
339 nd_error(nhdl, "Error looking up notification "
340 "preferences (%s)", scf_strerror(ret));
341 nd_dump_nvlist(nhdl, top_nvl);
342 goto pref_done;
343 }
344 }
345
346 if (nvlist_lookup_nvlist_array(top_nvl, SCF_NOTIFY_PARAMS, &np_nvlarr,
347 &nelem) != 0) {
348 nd_error(nhdl, "Malformed nvlist");
349 nd_dump_nvlist(nhdl, top_nvl);
350 ret = 1;
351 goto pref_done;
352 }
353 *pref_nvl = malloc(nelem * sizeof (nvlist_t *));
354 *nprefs = 0;
355
356 for (int i = 0; i < nelem; i++) {
357 if (nvlist_lookup_nvlist(np_nvlarr[i], mech, &mech_nvl) == 0) {
358 (void) nvlist_dup(mech_nvl, *pref_nvl + *nprefs, 0);
359 ++*nprefs;
360 }
361 }
362
363 if (*nprefs == 0) {
364 nd_debug(nhdl, "No %s notification preferences specified",
365 mech);
366 free(*pref_nvl);
367 ret = SCF_ERROR_NOT_FOUND;
368 goto pref_done;
369 }
370 ret = 0;
371 pref_done:
372 nvlist_free(top_nvl);
373 return (ret);
374 }
375
376 static int
377 nd_seq_search(char *key, char **list, uint_t nelem)
378 {
379 for (int i = 0; i < nelem; i++)
380 if (strcmp(key, list[i]) == 0)
381 return (1);
382 return (0);
383 }
384
385 /*
386 * This function takes a single string list and splits it into
387 * an string array (analogous to PERL split)
388 *
389 * The caller is responsible for freeing the array.
390 */
391 int
392 nd_split_list(nd_hdl_t *nhdl, char *list, char *delim, char ***arr,
393 uint_t *nelem)
394 {
395 char *item, *tmpstr;
396 int i = 1, size = 1;
397
398 tmpstr = strdup(list);
399 item = strtok(tmpstr, delim);
400 while (item && strtok(NULL, delim) != NULL)
401 size++;
402 free(tmpstr);
403
404 if ((*arr = calloc(size, sizeof (char *))) == NULL) {
405 nd_error(nhdl, "Error allocating memory (%s)", strerror(errno));
406 return (-1);
407 }
408 if (size == 1)
409 (*arr)[0] = strdup(list);
410 else {
411 tmpstr = strdup(list);
412 item = strtok(tmpstr, delim);
413 (*arr)[0] = strdup(item);
414 while ((item = strtok(NULL, delim)) != NULL)
415 (*arr)[i++] = strdup(item);
416 free(tmpstr);
417 }
418 *nelem = size;
419 return (0);
420 }
421
422 /*
423 * This function merges two string arrays into a single array, removing any
424 * duplicates
425 *
426 * The caller is responsible for freeing the merged array.
427 */
428 int
429 nd_merge_strarray(nd_hdl_t *nhdl, char **arr1, uint_t n1, char **arr2,
430 uint_t n2, char ***buf)
431 {
432 char **tmparr;
433 int uniq = -1;
434
435 tmparr = alloca((n1 + n2) * sizeof (char *));
436 bzero(tmparr, (n1 + n2) * sizeof (char *));
437
438 while (++uniq < n1)
439 tmparr[uniq] = strdup(arr1[uniq]);
440
441 for (int j = 0; j < n2; j++)
442 if (!nd_seq_search(arr2[j], tmparr, uniq))
443 tmparr[uniq++] = strdup(arr2[j]);
444
445 if ((*buf = calloc(uniq, sizeof (char *))) == NULL) {
446 nd_error(nhdl, "Error allocating memory (%s)", strerror(errno));
447 for (int j = 0; j < uniq; j++) {
448 if (tmparr[j])
449 free(tmparr[j]);
450 }
451 return (-1);
452 }
453
454 bcopy(tmparr, *buf, uniq * sizeof (char *));
455 return (uniq);
456 }
457
458 void
459 nd_free_strarray(char **arr, uint_t arrsz)
460 {
461 for (uint_t i = 0; i < arrsz; i++)
462 free(arr[i]);
463 free(arr);
464 }
465
466 /*
467 * This function joins all the strings in a string array into a single string
468 * Each element will be delimited by a comma
469 *
470 * The caller is responsible for freeing the joined string.
471 */
472 int
473 nd_join_strarray(nd_hdl_t *nhdl, char **arr, uint_t arrsz, char **buf)
474 {
475 uint_t len = 0;
476 char *jbuf;
477 int i;
478
479 /*
480 * First, figure out how much space we need to allocate to store the
481 * joined string.
482 */
483 for (i = 0; i < arrsz; i++)
484 len += strlen(arr[i]) + 1;
485
486 if ((jbuf = calloc(len, sizeof (char))) == NULL) {
487 nd_error(nhdl, "Error allocating memory (%s)", strerror(errno));
488 return (-1);
489 }
490
491 (void) snprintf(jbuf, len, "%s", arr[0]);
492 for (i = 1; i < arrsz; i++)
493 (void) snprintf(jbuf, len, "%s,%s", jbuf, arr[i]);
494
495 *buf = jbuf;
496 return (0);
497 }
498
499 void
500 nd_free_nvlarray(nvlist_t **arr, uint_t arrsz)
501 {
502 for (uint_t i = 0; i < arrsz; i++)
503 nvlist_free(arr[i]);
504 free(arr);
505 }
506
507 /*
508 * This function takes a dictionary name and event class and then uses
509 * libdiagcode to compute the MSG ID. We need this for looking up messages
510 * for the committed ireport.* events. For FMA list.* events, the MSG ID is
511 * is contained in the event payload.
512 */
513 int
514 nd_get_diagcode(nd_hdl_t *nhdl, const char *dict, const char *class, char *buf,
515 size_t buflen)
516 {
517 fm_dc_handle_t *dhp;
518 size_t dlen;
519 char *dirpath;
520 const char *key[2];
521 int ret = 0;
522
523 dlen = (strlen(nhdl->nh_rootdir) + strlen(ND_DICTDIR) + 2);
524 dirpath = alloca(dlen);
525 (void) snprintf(dirpath, dlen, "%s/%s", nhdl->nh_rootdir, ND_DICTDIR);
526
527 if ((dhp = fm_dc_opendict(FM_DC_VERSION, dirpath, dict)) == NULL) {
528 nd_error(nhdl, "fm_dc_opendict failed for %s/%s",
529 dirpath, dict);
530 return (-1);
531 }
532
533 key[0] = class;
534 key[1] = NULL;
535 if (fm_dc_key2code(dhp, key, buf, buflen) < 0) {
536 nd_error(nhdl, "fm_dc_key2code failed for %s", key[0]);
537 ret = -1;
538 }
539 fm_dc_closedict(dhp);
540 return (ret);
541 }
542
543 /*
544 * This function takes an event and extracts the bits of the event payload that
545 * are of interest to notification daemons and conveniently tucks them into a
546 * single struct.
547 *
548 * The caller is responsible for freeing ev_info and any contained strings and
549 * nvlists. A convenience function, nd_free_event_info(), is provided for this
550 * purpose.
551 */
552 int
553 nd_get_event_info(nd_hdl_t *nhdl, const char *class, fmev_t ev,
554 nd_ev_info_t **ev_info)
555 {
556 nvlist_t *attr_nvl;
557 nd_ev_info_t *evi;
558 char *code, *uuid, *from_state, *to_state, *reason;
559
560 if ((evi = calloc(1, sizeof (nd_ev_info_t))) == NULL) {
561 nd_error(nhdl, "Failed to allocate memory");
562 return (-1);
563 }
564
565 /*
566 * Hold event; class and payload will be valid for as long as
567 * we hold the event.
568 */
569 fmev_hold(ev);
570
571 evi->ei_ev = ev;
572 evi->ei_class = fmev_class(ev);
573 evi->ei_payload = fmev_attr_list(ev);
574
575 if (nvlist_lookup_string(evi->ei_payload, FM_SUSPECT_UUID,
576 &uuid) == 0) {
577 evi->ei_uuid = strdup(uuid);
578 } else {
579 nd_error(nhdl, "Malformed event");
580 nd_dump_nvlist(nhdl, evi->ei_payload);
581 nd_free_event_info(evi);
582 return (-1);
583 }
584
585 /*
586 * Lookup the MSGID, type, severity, description, and KA URL.
587 *
588 * For FMA list.* events we just pull it out of the the event nvlist.
589 * For all other events we call a utility function that computes the
590 * diagcode using the dict name and class.
591 */
592 evi->ei_diagcode = calloc(32, sizeof (char));
593 if ((nvlist_lookup_string(evi->ei_payload, FM_SUSPECT_DIAG_CODE,
594 &code) == 0 && strcpy(evi->ei_diagcode, code) != NULL) ||
595 nd_get_diagcode(nhdl, "SMF", class, evi->ei_diagcode, 32) == 0) {
596 evi->ei_type = fmd_msg_getitem_id(nhdl->nh_msghdl,
597 NULL, evi->ei_diagcode, FMD_MSG_ITEM_TYPE);
598 evi->ei_severity = fmd_msg_getitem_id(nhdl->nh_msghdl,
599 NULL, evi->ei_diagcode, FMD_MSG_ITEM_SEVERITY);
600 evi->ei_descr = fmd_msg_getitem_id(nhdl->nh_msghdl,
601 NULL, evi->ei_diagcode, FMD_MSG_ITEM_DESC);
602 evi->ei_url = fmd_msg_getitem_id(nhdl->nh_msghdl,
603 NULL, evi->ei_diagcode, FMD_MSG_ITEM_URL);
604 } else
605 (void) strcpy(evi->ei_diagcode, ND_UNKNOWN);
606
607 if (evi->ei_type == NULL)
608 evi->ei_type = strdup(ND_UNKNOWN);
609 if (evi->ei_severity == NULL)
610 evi->ei_severity = strdup(ND_UNKNOWN);
611 if (evi->ei_descr == NULL)
612 evi->ei_descr = strdup(ND_UNKNOWN);
613 if (evi->ei_url == NULL)
614 evi->ei_url = strdup(ND_UNKNOWN);
615
616 if ((evi->ei_fmri = nd_get_event_fmri(nhdl, ev)) == NULL)
617 evi->ei_fmri = strdup(ND_UNKNOWN);
618
619 if (strncmp(class, "ireport.os.smf", 14) == 0) {
620 if (nvlist_lookup_nvlist(evi->ei_payload, "attr",
621 &attr_nvl) != 0 ||
622 nvlist_lookup_string(attr_nvl, "from-state",
623 &from_state) != 0 ||
624 nvlist_lookup_string(attr_nvl, "to-state",
625 &to_state) != 0 ||
626 nvlist_lookup_string(attr_nvl, "reason-long",
627 &reason) != 0) {
628 nd_error(nhdl, "Malformed event");
629 nd_dump_nvlist(nhdl, evi->ei_payload);
630 nd_free_event_info(evi);
631 return (-1);
632 }
633 evi->ei_to_state = strdup(to_state);
634 evi->ei_from_state = strdup(from_state);
635 evi->ei_reason = strdup(reason);
636 }
637 *ev_info = evi;
638 return (0);
639 }
640
641 static void
642 condfree(void *buf)
643 {
644 if (buf != NULL)
645 free(buf);
646 }
647
648 void
649 nd_free_event_info(nd_ev_info_t *ev_info)
650 {
651 condfree(ev_info->ei_severity);
652 condfree(ev_info->ei_descr);
653 condfree(ev_info->ei_diagcode);
654 condfree(ev_info->ei_url);
655 condfree(ev_info->ei_uuid);
656 condfree(ev_info->ei_fmri);
657 condfree(ev_info->ei_from_state);
658 condfree(ev_info->ei_to_state);
659 condfree(ev_info->ei_reason);
660 fmev_rele(ev_info->ei_ev);
661 free(ev_info);
662 }