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) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
24 */
25
26 /*
27 * FMD Message Library
28 *
29 * This library supports a simple set of routines for use in converting FMA
30 * events and message codes to localized human-readable message strings.
31 *
32 * 1. Library API
33 *
34 * The APIs are as follows:
35 *
36 * fmd_msg_init - set up the library and return a handle
37 * fmd_msg_fini - destroy the handle from fmd_msg_init
38 *
39 * fmd_msg_locale_set - set the default locale (initially based on environ(5))
40 * fmd_msg_locale_get - get the default locale
41 *
42 * fmd_msg_url_set - set the default URL for knowledge articles
43 * fmd_msg_url_get - get the default URL for knowledge articles
44 *
45 * fmd_msg_gettext_nv - format the entire message for the given event
46 * fmd_msg_gettext_id - format the entire message for the given event code
47 * fmd_msg_gettext_key - format the entire message for the given dict for the
48 * given explicit message key
49 *
50 * fmd_msg_getitem_nv - format a single message item for the given event
51 * fmd_msg_getitem_id - format a single message item for the given event code
52 *
53 * Upon success, fmd_msg_gettext_* and fmd_msg_getitem_* return newly-allocated
54 * localized strings in multi-byte format. The caller must call free() on the
55 * resulting buffer to deallocate the string after making use of it. Upon
56 * failure, these functions return NULL and set errno as follows:
57 *
58 * ENOMEM - Memory allocation failure while formatting message
59 * ENOENT - No message was found for the specified message identifier
60 * EINVAL - Invalid argument (e.g. bad event code, illegal fmd_msg_item_t)
61 * EILSEQ - Illegal multi-byte sequence detected in message
62 *
63 * 2. Variable Expansion
64 *
65 * The human-readable messages are stored in msgfmt(1) message object files in
66 * the corresponding locale directories. The values for the message items are
67 * permitted to contain variable expansions, currently defined as follows:
68 *
69 * %% - literal % character
70 * %s - knowledge article URL (e.g. http://illumos.org/msg/<MSG-ID>)
71 * %< x > - value x from the current event, using the expression syntax below:
72 *
73 * foo.bar => print nvlist_t member "bar" contained within nvlist_t "foo"
74 * foo[123] => print array element 123 of nvlist_t member "foo"
75 * foo[123].bar => print member "bar" of nvlist_t element 123 in array "foo"
76 *
77 * For example, the msgstr value for FMD-8000-2K might be defined as:
78 *
79 * msgid "FMD-8000-2K.action"
80 * msgstr "Use fmdump -v -u %<uuid> to locate the module. Use fmadm \
81 * reset %<fault-list[0].asru.mod-name> to reset the module."
82 *
83 * 3. Locking
84 *
85 * In order to format a human-readable message, libfmd_msg must get and set
86 * the process locale and potentially alter text domain bindings. At present,
87 * these facilities in libc are not fully MT-safe. As such, a library-wide
88 * lock is provided: fmd_msg_lock() and fmd_msg_unlock(). These locking calls
89 * are made internally as part of the top-level library entry points, but they
90 * can also be used by applications that themselves call setlocale() and wish
91 * to appropriately synchronize with other threads that are calling libfmd_msg.
92 */
93
94
95 #include <sys/fm/protocol.h>
96
97 #include <libintl.h>
98 #include <locale.h>
99 #include <wchar.h>
100
101 #include <alloca.h>
102 #include <assert.h>
103 #include <netdb.h>
104 #include <pthread.h>
105 #include <synch.h>
106 #include <strings.h>
107 #include <stdarg.h>
108 #include <stdlib.h>
109 #include <stdio.h>
110 #include <errno.h>
111 #include <unistd.h>
112 #include <sys/sysmacros.h>
113
114 #include <fmd_msg.h>
115
116 #define FMD_MSGBUF_SZ 256
117
118 struct fmd_msg_hdl {
119 int fmh_version; /* libfmd_msg client abi version number */
120 char *fmh_urlbase; /* base url for all knowledge articles */
121 char *fmh_binding; /* base directory for bindtextdomain() */
122 char *fmh_locale; /* default program locale from environment */
123 const char *fmh_template; /* FMD_MSG_TEMPLATE value for fmh_locale */
124 };
125
126 typedef struct fmd_msg_buf {
127 wchar_t *fmb_data; /* wide-character data buffer */
128 size_t fmb_size; /* size of fmb_data in wchar_t units */
129 size_t fmb_used; /* used portion of fmb_data in wchar_t units */
130 int fmb_error; /* error if any has occurred */
131 } fmd_msg_buf_t;
132
133 static const char *const fmd_msg_items[] = {
134 "type", /* key for FMD_MSG_ITEM_TYPE */
135 "severity", /* key for FMD_MSG_ITEM_SEVERITY */
136 "description", /* key for FMD_MSG_ITEM_DESC */
137 "response", /* key for FMD_MSG_ITEM_RESPONSE */
138 "impact", /* key for FMD_MSG_ITEM_IMPACT */
139 "action", /* key for FMD_MSG_ITEM_ACTION */
140 "url", /* key for FMD_MSG_ITEM_URL */
141 };
142
143 static pthread_rwlock_t fmd_msg_rwlock = PTHREAD_RWLOCK_INITIALIZER;
144
145 static const char FMD_MSG_DOMAIN[] = "FMD";
146 static const char FMD_MSG_TEMPLATE[] = "syslog-msgs-message-template";
147 static const char FMD_MSG_URLKEY[] = "syslog-url";
148 static const char FMD_MSG_URLBASE[] = "http://illumos.org/msg/";
149 static const char FMD_MSG_NLSPATH[] = "NLSPATH=/usr/lib/fm/fmd/fmd.cat";
150 static const char FMD_MSG_MISSING[] = "-";
151
152 /*
153 * An enumeration of token types. The following are valid tokens that can be
154 * embedded into the message content:
155 *
156 * T_INT - integer tokens (for array indices)
157 * T_IDENT - nvpair identifiers
158 * T_DOT - "."
159 * T_LBRAC - "["
160 * T_RBRAC - "]"
161 *
162 * A NULL character (T_EOF) is used to terminate messages.
163 * Invalid tokens are assigned the type T_ERR.
164 */
165 typedef enum {
166 T_EOF,
167 T_ERR,
168 T_IDENT,
169 T_INT,
170 T_DOT,
171 T_LBRAC,
172 T_RBRAC
173 } fmd_msg_nv_tkind_t;
174
175 typedef struct fmd_msg_nv_token {
176 fmd_msg_nv_tkind_t t_kind;
177 union {
178 char tu_str[256];
179 uint_t tu_int;
180 } t_data;
181 } fmd_msg_nv_token_t;
182
183 static const struct fmd_msg_nv_type {
184 data_type_t nvt_type;
185 data_type_t nvt_base;
186 size_t nvt_size;
187 int (*nvt_value)();
188 int (*nvt_array)();
189 } fmd_msg_nv_types[] = {
190 { DATA_TYPE_INT8, DATA_TYPE_INT8,
191 sizeof (int8_t), nvpair_value_int8, NULL },
192 { DATA_TYPE_INT16, DATA_TYPE_INT16,
193 sizeof (int16_t), nvpair_value_int16, NULL },
194 { DATA_TYPE_INT32, DATA_TYPE_INT32,
195 sizeof (int32_t), nvpair_value_int32, NULL },
196 { DATA_TYPE_INT64, DATA_TYPE_INT64,
197 sizeof (int64_t), nvpair_value_int64, NULL },
198 { DATA_TYPE_UINT8, DATA_TYPE_UINT8,
199 sizeof (uint8_t), nvpair_value_uint8, NULL },
200 { DATA_TYPE_UINT16, DATA_TYPE_UINT16,
201 sizeof (uint16_t), nvpair_value_uint16, NULL },
202 { DATA_TYPE_UINT32, DATA_TYPE_UINT32,
203 sizeof (uint32_t), nvpair_value_uint32, NULL },
204 { DATA_TYPE_UINT64, DATA_TYPE_UINT64,
205 sizeof (uint64_t), nvpair_value_uint64, NULL },
206 { DATA_TYPE_BYTE, DATA_TYPE_BYTE,
207 sizeof (uchar_t), nvpair_value_byte, NULL },
208 { DATA_TYPE_BOOLEAN, DATA_TYPE_BOOLEAN,
209 0, NULL, NULL },
210 { DATA_TYPE_BOOLEAN_VALUE, DATA_TYPE_BOOLEAN_VALUE,
211 sizeof (boolean_t), nvpair_value_boolean_value, NULL },
212 { DATA_TYPE_HRTIME, DATA_TYPE_HRTIME,
213 sizeof (hrtime_t), nvpair_value_hrtime, NULL },
214 { DATA_TYPE_STRING, DATA_TYPE_STRING,
215 sizeof (char *), nvpair_value_string, NULL },
216 { DATA_TYPE_NVLIST, DATA_TYPE_NVLIST,
217 sizeof (nvlist_t *), nvpair_value_nvlist, NULL },
218 { DATA_TYPE_INT8_ARRAY, DATA_TYPE_INT8,
219 sizeof (int8_t), NULL, nvpair_value_int8_array },
220 { DATA_TYPE_INT16_ARRAY, DATA_TYPE_INT16,
221 sizeof (int16_t), NULL, nvpair_value_int16_array },
222 { DATA_TYPE_INT32_ARRAY, DATA_TYPE_INT32,
223 sizeof (int32_t), NULL, nvpair_value_int32_array },
224 { DATA_TYPE_INT64_ARRAY, DATA_TYPE_INT64,
225 sizeof (int64_t), NULL, nvpair_value_int64_array },
226 { DATA_TYPE_UINT8_ARRAY, DATA_TYPE_UINT8,
227 sizeof (uint8_t), NULL, nvpair_value_uint8_array },
228 { DATA_TYPE_UINT16_ARRAY, DATA_TYPE_UINT16,
229 sizeof (uint16_t), NULL, nvpair_value_uint16_array },
230 { DATA_TYPE_UINT32_ARRAY, DATA_TYPE_UINT32,
231 sizeof (uint32_t), NULL, nvpair_value_uint32_array },
232 { DATA_TYPE_UINT64_ARRAY, DATA_TYPE_UINT64,
233 sizeof (uint64_t), NULL, nvpair_value_uint64_array },
234 { DATA_TYPE_BYTE_ARRAY, DATA_TYPE_BYTE,
235 sizeof (uchar_t), NULL, nvpair_value_byte_array },
236 { DATA_TYPE_BOOLEAN_ARRAY, DATA_TYPE_BOOLEAN_VALUE,
237 sizeof (boolean_t), NULL, nvpair_value_boolean_array },
238 { DATA_TYPE_STRING_ARRAY, DATA_TYPE_STRING,
239 sizeof (char *), NULL, nvpair_value_string_array },
240 { DATA_TYPE_NVLIST_ARRAY, DATA_TYPE_NVLIST,
241 sizeof (nvlist_t *), NULL, nvpair_value_nvlist_array },
242 { DATA_TYPE_UNKNOWN, DATA_TYPE_UNKNOWN, 0, NULL, NULL }
243 };
244
245 static int fmd_msg_nv_parse_nvpair(fmd_msg_buf_t *, nvpair_t *, char *);
246 static int fmd_msg_nv_parse_nvname(fmd_msg_buf_t *, nvlist_t *, char *);
247 static int fmd_msg_nv_parse_nvlist(fmd_msg_buf_t *, nvlist_t *, char *);
248
249 /*ARGSUSED*/
250 static int
fmd_msg_lock_held(fmd_msg_hdl_t * h)251 fmd_msg_lock_held(fmd_msg_hdl_t *h)
252 {
253 return (RW_WRITE_HELD(&fmd_msg_rwlock));
254 }
255
256 void
fmd_msg_lock(void)257 fmd_msg_lock(void)
258 {
259 if (pthread_rwlock_wrlock(&fmd_msg_rwlock) != 0)
260 abort();
261 }
262
263 void
fmd_msg_unlock(void)264 fmd_msg_unlock(void)
265 {
266 if (pthread_rwlock_unlock(&fmd_msg_rwlock) != 0)
267 abort();
268 }
269
270 static fmd_msg_hdl_t *
fmd_msg_init_err(fmd_msg_hdl_t * h,int err)271 fmd_msg_init_err(fmd_msg_hdl_t *h, int err)
272 {
273 fmd_msg_fini(h);
274 errno = err;
275 return (NULL);
276 }
277
278 fmd_msg_hdl_t *
fmd_msg_init(const char * root,int version)279 fmd_msg_init(const char *root, int version)
280 {
281 fmd_msg_hdl_t *h = NULL;
282 const char *s;
283 size_t len;
284
285 if (version != FMD_MSG_VERSION)
286 return (fmd_msg_init_err(h, EINVAL));
287
288 if ((h = malloc(sizeof (fmd_msg_hdl_t))) == NULL)
289 return (fmd_msg_init_err(h, ENOMEM));
290
291 bzero(h, sizeof (fmd_msg_hdl_t));
292 h->fmh_version = version;
293
294 if ((h->fmh_urlbase = strdup(FMD_MSG_URLBASE)) == NULL)
295 return (fmd_msg_init_err(h, ENOMEM));
296
297 /*
298 * Initialize the program's locale from the environment if it hasn't
299 * already been initialized, and then retrieve the default setting.
300 */
301 (void) setlocale(LC_ALL, "");
302 s = setlocale(LC_ALL, NULL);
303 h->fmh_locale = strdup(s ? s : "C");
304
305 if (h->fmh_locale == NULL)
306 return (fmd_msg_init_err(h, ENOMEM));
307
308 /*
309 * If a non-default root directory is specified, then look up the base
310 * directory for our default catalog, and set fmh_binding as the same
311 * directory prefixed with the new root directory. This simply turns
312 * usr/lib/locale into <rootdir>/usr/lib/locale, but handles all of the
313 * environ(5) settings that can change the default messages binding.
314 */
315 if (root != NULL && root[0] != '\0' && strcmp(root, "/") != 0) {
316 if (root[0] != '/')
317 return (fmd_msg_init_err(h, EINVAL));
318
319 if ((s = bindtextdomain(FMD_MSG_DOMAIN, NULL)) == NULL)
320 s = "/usr/lib/locale"; /* substitute default */
321
322 len = strlen(root) + strlen(s) + 1;
323
324 if ((h->fmh_binding = malloc(len)) == NULL)
325 return (fmd_msg_init_err(h, ENOMEM));
326
327 (void) snprintf(h->fmh_binding, len, "%s%s", root, s);
328 }
329
330 /*
331 * All FMA event dictionaries use msgfmt(1) message objects to produce
332 * messages, even for the C locale. We therefore want to use dgettext
333 * for all message lookups, but its defined behavior in the C locale is
334 * to return the input string. Since our input strings are event codes
335 * and not format strings, this doesn't help us. We resolve this nit
336 * by setting NLSPATH to a non-existent file: the presence of NLSPATH
337 * is defined to force dgettext(3C) to do a full lookup even for C.
338 */
339 if (getenv("NLSPATH") == NULL &&
340 ((s = strdup(FMD_MSG_NLSPATH)) == NULL || putenv((char *)s) != 0))
341 return (fmd_msg_init_err(h, errno));
342
343 /*
344 * Cache the message template for the current locale. This is the
345 * snprintf(3C) format string for the final human-readable message.
346 * If the lookup fails for the current locale, fall back to the C locale
347 * and try again. Then restore the original locale.
348 */
349 if ((h->fmh_template = dgettext(FMD_MSG_DOMAIN, FMD_MSG_TEMPLATE))
350 == FMD_MSG_TEMPLATE && strcmp(h->fmh_locale, "C") != 0) {
351 (void) setlocale(LC_ALL, "C");
352 h->fmh_template = dgettext(FMD_MSG_DOMAIN, FMD_MSG_TEMPLATE);
353 (void) setlocale(LC_ALL, h->fmh_locale);
354 }
355
356 return (h);
357 }
358
359 void
fmd_msg_fini(fmd_msg_hdl_t * h)360 fmd_msg_fini(fmd_msg_hdl_t *h)
361 {
362 if (h == NULL)
363 return; /* simplify caller code */
364
365 free(h->fmh_binding);
366 free(h->fmh_urlbase);
367 free(h->fmh_locale);
368 free(h);
369 }
370
371 int
fmd_msg_locale_set(fmd_msg_hdl_t * h,const char * locale)372 fmd_msg_locale_set(fmd_msg_hdl_t *h, const char *locale)
373 {
374 char *l;
375
376 if (locale == NULL) {
377 errno = EINVAL;
378 return (-1);
379 }
380
381 if ((l = strdup(locale)) == NULL) {
382 errno = ENOMEM;
383 return (-1);
384 }
385
386 fmd_msg_lock();
387
388 if (setlocale(LC_ALL, l) == NULL) {
389 free(l);
390 errno = EINVAL;
391 fmd_msg_unlock();
392 return (-1);
393 }
394
395 h->fmh_template = dgettext(FMD_MSG_DOMAIN, FMD_MSG_TEMPLATE);
396 free(h->fmh_locale);
397 h->fmh_locale = l;
398
399 fmd_msg_unlock();
400 return (0);
401 }
402
403 const char *
fmd_msg_locale_get(fmd_msg_hdl_t * h)404 fmd_msg_locale_get(fmd_msg_hdl_t *h)
405 {
406 return (h->fmh_locale);
407 }
408
409 int
fmd_msg_url_set(fmd_msg_hdl_t * h,const char * url)410 fmd_msg_url_set(fmd_msg_hdl_t *h, const char *url)
411 {
412 char *u;
413
414 if (url == NULL) {
415 errno = EINVAL;
416 return (-1);
417 }
418
419 if ((u = strdup(url)) == NULL) {
420 errno = ENOMEM;
421 return (-1);
422 }
423
424 fmd_msg_lock();
425
426 free(h->fmh_urlbase);
427 h->fmh_urlbase = u;
428
429 fmd_msg_unlock();
430 return (0);
431 }
432
433 const char *
fmd_msg_url_get(fmd_msg_hdl_t * h)434 fmd_msg_url_get(fmd_msg_hdl_t *h)
435 {
436 return (h->fmh_urlbase);
437 }
438
439 static wchar_t *
fmd_msg_mbstowcs(const char * s)440 fmd_msg_mbstowcs(const char *s)
441 {
442 size_t n = strlen(s) + 1;
443 wchar_t *w = malloc(n * sizeof (wchar_t));
444
445 if (w == NULL) {
446 errno = ENOMEM;
447 return (NULL);
448 }
449
450 if (mbstowcs(w, s, n) == (size_t)-1) {
451 free(w);
452 return (NULL);
453 }
454
455 return (w);
456 }
457
458 static void
fmd_msg_buf_init(fmd_msg_buf_t * b)459 fmd_msg_buf_init(fmd_msg_buf_t *b)
460 {
461 bzero(b, sizeof (fmd_msg_buf_t));
462 b->fmb_data = malloc(sizeof (wchar_t) * FMD_MSGBUF_SZ);
463
464 if (b->fmb_data == NULL)
465 b->fmb_error = ENOMEM;
466 else
467 b->fmb_size = FMD_MSGBUF_SZ;
468 }
469
470 static void
fmd_msg_buf_fini(fmd_msg_buf_t * b)471 fmd_msg_buf_fini(fmd_msg_buf_t *b)
472 {
473 free(b->fmb_data);
474 bzero(b, sizeof (fmd_msg_buf_t));
475 }
476
477 static char *
fmd_msg_buf_read(fmd_msg_buf_t * b)478 fmd_msg_buf_read(fmd_msg_buf_t *b)
479 {
480 char *s;
481
482 if (b->fmb_error != 0) {
483 errno = b->fmb_error;
484 return (NULL);
485 }
486
487 if ((s = malloc(b->fmb_used * MB_CUR_MAX)) == NULL) {
488 errno = ENOMEM;
489 return (NULL);
490 }
491
492 if (wcstombs(s, b->fmb_data, b->fmb_used) == (size_t)-1) {
493 free(s);
494 return (NULL);
495 }
496
497 return (s);
498 }
499
500 /*
501 * Buffer utility function to write a wide-character string into the buffer,
502 * appending it at the end, and growing the buffer as needed as we go. Any
503 * allocation errors are stored in fmb_error and deferred until later.
504 */
505 static void
fmd_msg_buf_write(fmd_msg_buf_t * b,const wchar_t * w,size_t n)506 fmd_msg_buf_write(fmd_msg_buf_t *b, const wchar_t *w, size_t n)
507 {
508 if (b->fmb_used + n > b->fmb_size) {
509 size_t size = MAX(b->fmb_size * 2, b->fmb_used + n);
510 wchar_t *data = malloc(sizeof (wchar_t) * size);
511
512 if (data == NULL) {
513 if (b->fmb_error == 0)
514 b->fmb_error = ENOMEM;
515 return;
516 }
517
518 bcopy(b->fmb_data, data, b->fmb_used * sizeof (wchar_t));
519 free(b->fmb_data);
520
521 b->fmb_data = data;
522 b->fmb_size = size;
523 }
524
525 bcopy(w, &b->fmb_data[b->fmb_used], sizeof (wchar_t) * n);
526 b->fmb_used += n;
527 }
528
529 /*
530 * Buffer utility function to printf a multi-byte string, convert to wide-
531 * character form, and then write the result into an fmd_msg_buf_t.
532 */
533 /*PRINTFLIKE2*/
534 static void
fmd_msg_buf_printf(fmd_msg_buf_t * b,const char * format,...)535 fmd_msg_buf_printf(fmd_msg_buf_t *b, const char *format, ...)
536 {
537 ssize_t len;
538 va_list ap;
539 char *buf;
540 wchar_t *w;
541
542 va_start(ap, format);
543 len = vsnprintf(NULL, 0, format, ap);
544 buf = alloca(len + 1);
545 (void) vsnprintf(buf, len + 1, format, ap);
546 va_end(ap);
547
548 if ((w = fmd_msg_mbstowcs(buf)) == NULL) {
549 if (b->fmb_error != 0)
550 b->fmb_error = errno;
551 } else {
552 fmd_msg_buf_write(b, w, wcslen(w));
553 free(w);
554 }
555 }
556
557 /*PRINTFLIKE1*/
558 static int
fmd_msg_nv_error(const char * format,...)559 fmd_msg_nv_error(const char *format, ...)
560 {
561 int err = errno;
562 va_list ap;
563
564 if (getenv("FMD_MSG_DEBUG") == NULL)
565 return (1);
566
567 (void) fprintf(stderr, "libfmd_msg DEBUG: ");
568 va_start(ap, format);
569 (void) vfprintf(stderr, format, ap);
570 va_end(ap);
571
572 if (strchr(format, '\n') == NULL)
573 (void) fprintf(stderr, ": %s\n", strerror(err));
574
575 return (1);
576 }
577
578 static const struct fmd_msg_nv_type *
fmd_msg_nv_type_lookup(data_type_t type)579 fmd_msg_nv_type_lookup(data_type_t type)
580 {
581 const struct fmd_msg_nv_type *t;
582
583 for (t = fmd_msg_nv_types; t->nvt_type != DATA_TYPE_UNKNOWN; t++) {
584 if (t->nvt_type == type)
585 break;
586 }
587
588 return (t);
589 }
590
591 /*
592 * Print the specified string, escaping any unprintable character sequences
593 * using the ISO C character escape sequences.
594 */
595 static void
fmd_msg_nv_print_string(fmd_msg_buf_t * b,const char * s)596 fmd_msg_nv_print_string(fmd_msg_buf_t *b, const char *s)
597 {
598 char c;
599
600 while ((c = *s++) != '\0') {
601 if (c >= ' ' && c <= '~' && c != '\'') {
602 fmd_msg_buf_printf(b, "%c", c);
603 continue;
604 }
605
606 switch (c) {
607 case '\0':
608 fmd_msg_buf_printf(b, "\\0");
609 break;
610 case '\a':
611 fmd_msg_buf_printf(b, "\\a");
612 break;
613 case '\b':
614 fmd_msg_buf_printf(b, "\\b");
615 break;
616 case '\f':
617 fmd_msg_buf_printf(b, "\\f");
618 break;
619 case '\n':
620 fmd_msg_buf_printf(b, "\\n");
621 break;
622 case '\r':
623 fmd_msg_buf_printf(b, "\\r");
624 break;
625 case '\t':
626 fmd_msg_buf_printf(b, "\\t");
627 break;
628 case '\v':
629 fmd_msg_buf_printf(b, "\\v");
630 break;
631 case '\'':
632 fmd_msg_buf_printf(b, "\\'");
633 break;
634 case '"':
635 fmd_msg_buf_printf(b, "\\\"");
636 break;
637 case '\\':
638 fmd_msg_buf_printf(b, "\\\\");
639 break;
640 default:
641 fmd_msg_buf_printf(b, "\\x%02x", (uchar_t)c);
642 }
643 }
644 }
645
646 /*
647 * Print the value of the specified nvpair into the supplied buffer.
648 *
649 * For nvpairs that are arrays types, passing -1 as the idx param indicates
650 * that we want to print all of the elements in the array.
651 *
652 * Returns 0 on success, 1 otherwise.
653 */
654 static int
fmd_msg_nv_print_items(fmd_msg_buf_t * b,nvpair_t * nvp,data_type_t type,void * p,uint_t n,uint_t idx)655 fmd_msg_nv_print_items(fmd_msg_buf_t *b, nvpair_t *nvp,
656 data_type_t type, void *p, uint_t n, uint_t idx)
657 {
658 const struct fmd_msg_nv_type *nvt = fmd_msg_nv_type_lookup(type);
659 uint_t i;
660
661 if (idx != -1u) {
662 if (idx >= n) {
663 return (fmd_msg_nv_error("index %u out-of-range for "
664 "array %s: valid range is [0 .. %u]\n",
665 idx, nvpair_name(nvp), n ? n - 1 : 0));
666 }
667 p = (uchar_t *)p + nvt->nvt_size * idx;
668 n = 1;
669 }
670
671 for (i = 0; i < n; i++, p = (uchar_t *)p + nvt->nvt_size) {
672 if (i > 0)
673 fmd_msg_buf_printf(b, " "); /* array item delimiter */
674
675 switch (type) {
676 case DATA_TYPE_INT8:
677 fmd_msg_buf_printf(b, "%d", *(int8_t *)p);
678 break;
679
680 case DATA_TYPE_INT16:
681 fmd_msg_buf_printf(b, "%d", *(int16_t *)p);
682 break;
683
684 case DATA_TYPE_INT32:
685 fmd_msg_buf_printf(b, "%d", *(int32_t *)p);
686 break;
687
688 case DATA_TYPE_INT64:
689 fmd_msg_buf_printf(b, "%lld", *(longlong_t *)p);
690 break;
691
692 case DATA_TYPE_UINT8:
693 fmd_msg_buf_printf(b, "%u", *(uint8_t *)p);
694 break;
695
696 case DATA_TYPE_UINT16:
697 fmd_msg_buf_printf(b, "%u", *(uint16_t *)p);
698 break;
699
700 case DATA_TYPE_UINT32:
701 fmd_msg_buf_printf(b, "%u", *(uint32_t *)p);
702 break;
703
704 case DATA_TYPE_UINT64:
705 fmd_msg_buf_printf(b, "%llu", *(u_longlong_t *)p);
706 break;
707
708 case DATA_TYPE_BYTE:
709 fmd_msg_buf_printf(b, "0x%x", *(uchar_t *)p);
710 break;
711
712 case DATA_TYPE_BOOLEAN_VALUE:
713 fmd_msg_buf_printf(b,
714 *(boolean_t *)p ? "true" : "false");
715 break;
716
717 case DATA_TYPE_HRTIME:
718 fmd_msg_buf_printf(b, "%lld", *(longlong_t *)p);
719 break;
720
721 case DATA_TYPE_STRING:
722 fmd_msg_nv_print_string(b, *(char **)p);
723 break;
724 }
725 }
726
727 return (0);
728 }
729
730 /*
731 * Writes the value of the specified nvpair to the supplied buffer.
732 *
733 * Returns 0 on success, 1 otherwise.
734 */
735 static int
fmd_msg_nv_print_nvpair(fmd_msg_buf_t * b,nvpair_t * nvp,uint_t idx)736 fmd_msg_nv_print_nvpair(fmd_msg_buf_t *b, nvpair_t *nvp, uint_t idx)
737 {
738 data_type_t type = nvpair_type(nvp);
739 const struct fmd_msg_nv_type *nvt = fmd_msg_nv_type_lookup(type);
740
741 uint64_t v;
742 void *a;
743 uint_t n;
744 int err;
745
746 if (nvt->nvt_type == DATA_TYPE_BOOLEAN) {
747 fmd_msg_buf_printf(b, "true");
748 err = 0;
749 } else if (nvt->nvt_array != NULL) {
750 (void) nvt->nvt_array(nvp, &a, &n);
751 err = fmd_msg_nv_print_items(b, nvp, nvt->nvt_base, a, n, idx);
752 } else if (nvt->nvt_value != NULL) {
753 (void) nvt->nvt_value(nvp, &v);
754 err = fmd_msg_nv_print_items(b, nvp, nvt->nvt_base, &v, 1, idx);
755 } else {
756 err = fmd_msg_nv_error("unknown data type %u", type);
757 }
758
759 return (err);
760 }
761
762 /*
763 * Consume a token from the specified string, fill in the specified token
764 * struct, and return the new string position from which to continue parsing.
765 */
766 static char *
fmd_msg_nv_parse_token(char * s,fmd_msg_nv_token_t * tp)767 fmd_msg_nv_parse_token(char *s, fmd_msg_nv_token_t *tp)
768 {
769 char *p = s, *q, c = *s;
770
771 /*
772 * Skip whitespace and then look for an integer token first. We can't
773 * use isspace() or isdigit() because we're in setlocale() context now.
774 */
775 while (c == ' ' || c == '\t' || c == '\v' || c == '\n' || c == '\r')
776 c = *++p;
777
778 if (c >= '0' && c <= '9') {
779 errno = 0;
780 tp->t_data.tu_int = strtoul(p, &q, 0);
781
782 if (errno != 0 || p == q) {
783 tp->t_kind = T_ERR;
784 return (p);
785 }
786
787 tp->t_kind = T_INT;
788 return (q);
789 }
790
791 /*
792 * Look for a name-value pair identifier, which we define to be the
793 * regular expression [a-zA-Z_][a-zA-Z0-9_-]* (NOTE: Ideally "-" would
794 * not be allowed here and we would require ISO C identifiers, but many
795 * FMA event members use hyphens.) This code specifically cannot use
796 * the isspace(), isalnum() etc. macros because we are currently in the
797 * context of an earlier call to setlocale() that may have installed a
798 * non-C locale, but this code needs to always operate on C characters.
799 */
800 if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_') {
801 for (q = p + 1; (c = *q) != '\0'; q++) {
802 if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') &&
803 (c < '0' || c > '9') && (c != '_' && c != '-'))
804 break;
805 }
806
807 if (sizeof (tp->t_data.tu_str) <= (size_t)(q - p)) {
808 tp->t_kind = T_ERR;
809 return (p);
810 }
811
812 bcopy(p, tp->t_data.tu_str, (size_t)(q - p));
813 tp->t_data.tu_str[(size_t)(q - p)] = '\0';
814 tp->t_kind = T_IDENT;
815 return (q);
816 }
817
818 switch (c) {
819 case '\0':
820 tp->t_kind = T_EOF;
821 return (p);
822 case '.':
823 tp->t_kind = T_DOT;
824 return (p + 1);
825 case '[':
826 tp->t_kind = T_LBRAC;
827 return (p + 1);
828 case ']':
829 tp->t_kind = T_RBRAC;
830 return (p + 1);
831 default:
832 tp->t_kind = T_ERR;
833 return (p);
834 }
835 }
836
837 static int
fmd_msg_nv_parse_error(const char * s,fmd_msg_nv_token_t * tp)838 fmd_msg_nv_parse_error(const char *s, fmd_msg_nv_token_t *tp)
839 {
840 if (tp->t_kind == T_ERR)
841 return (fmd_msg_nv_error("illegal character at \"%s\"\n", s));
842 else
843 return (fmd_msg_nv_error("syntax error near \"%s\"\n", s));
844 }
845
846 /*
847 * Parse an array expression for referencing an element of the specified
848 * nvpair_t, which is expected to be of an array type. If it's an array of
849 * intrinsics, print the specified value. If it's an array of nvlist_t's,
850 * call fmd_msg_nv_parse_nvlist() recursively to continue parsing.
851 */
852 static int
fmd_msg_nv_parse_array(fmd_msg_buf_t * b,nvpair_t * nvp,char * s1)853 fmd_msg_nv_parse_array(fmd_msg_buf_t *b, nvpair_t *nvp, char *s1)
854 {
855 fmd_msg_nv_token_t t;
856 nvlist_t **nva;
857 uint_t i, n;
858 char *s2;
859
860 if (fmd_msg_nv_type_lookup(nvpair_type(nvp))->nvt_array == NULL) {
861 return (fmd_msg_nv_error("inappropriate use of operator [ ]: "
862 "element '%s' is not an array\n", nvpair_name(nvp)));
863 }
864
865 s2 = fmd_msg_nv_parse_token(s1, &t);
866 i = t.t_data.tu_int;
867
868 if (t.t_kind != T_INT)
869 return (fmd_msg_nv_error("expected integer index after [\n"));
870
871 s2 = fmd_msg_nv_parse_token(s2, &t);
872
873 if (t.t_kind != T_RBRAC)
874 return (fmd_msg_nv_error("expected ] after [ %u\n", i));
875
876 /*
877 * An array of nvlist is different from other array types in that it
878 * permits us to continue parsing instead of printing a terminal node.
879 */
880 if (nvpair_type(nvp) == DATA_TYPE_NVLIST_ARRAY) {
881 (void) nvpair_value_nvlist_array(nvp, &nva, &n);
882
883 if (i >= n) {
884 return (fmd_msg_nv_error("index %u out-of-range for "
885 "array %s: valid range is [0 .. %u]\n",
886 i, nvpair_name(nvp), n ? n - 1 : 0));
887 }
888
889 return (fmd_msg_nv_parse_nvlist(b, nva[i], s2));
890 }
891
892 (void) fmd_msg_nv_parse_token(s2, &t);
893
894 if (t.t_kind != T_EOF) {
895 return (fmd_msg_nv_error("expected end-of-string "
896 "in expression instead of \"%s\"\n", s2));
897 }
898
899 return (fmd_msg_nv_print_nvpair(b, nvp, i));
900 }
901
902 /*
903 * Parse an expression rooted at an nvpair_t. If we see EOF, print the entire
904 * nvpair. If we see LBRAC, parse an array expression. If we see DOT, call
905 * fmd_msg_nv_parse_nvname() recursively to dereference an embedded member.
906 */
907 static int
fmd_msg_nv_parse_nvpair(fmd_msg_buf_t * b,nvpair_t * nvp,char * s1)908 fmd_msg_nv_parse_nvpair(fmd_msg_buf_t *b, nvpair_t *nvp, char *s1)
909 {
910 fmd_msg_nv_token_t t;
911 nvlist_t *nvl;
912 char *s2;
913
914 s2 = fmd_msg_nv_parse_token(s1, &t);
915
916 if (t.t_kind == T_EOF)
917 return (fmd_msg_nv_print_nvpair(b, nvp, -1));
918
919 if (t.t_kind == T_LBRAC)
920 return (fmd_msg_nv_parse_array(b, nvp, s2));
921
922 if (t.t_kind != T_DOT)
923 return (fmd_msg_nv_parse_error(s1, &t));
924
925 if (nvpair_type(nvp) != DATA_TYPE_NVLIST) {
926 return (fmd_msg_nv_error("inappropriate use of operator '.': "
927 "element '%s' is not of type nvlist\n", nvpair_name(nvp)));
928 }
929
930 (void) nvpair_value_nvlist(nvp, &nvl);
931 return (fmd_msg_nv_parse_nvname(b, nvl, s2));
932 }
933
934 /*
935 * Parse an expression for a name-value pair name (IDENT). If we find a match
936 * continue parsing with the corresponding nvpair_t.
937 */
938 static int
fmd_msg_nv_parse_nvname(fmd_msg_buf_t * b,nvlist_t * nvl,char * s1)939 fmd_msg_nv_parse_nvname(fmd_msg_buf_t *b, nvlist_t *nvl, char *s1)
940 {
941 nvpair_t *nvp = NULL;
942 fmd_msg_nv_token_t t;
943 char *s2;
944
945 s2 = fmd_msg_nv_parse_token(s1, &t);
946
947 if (t.t_kind != T_IDENT)
948 return (fmd_msg_nv_parse_error(s1, &t));
949
950 while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
951 if (strcmp(nvpair_name(nvp), t.t_data.tu_str) == 0)
952 break;
953 }
954
955 if (nvp == NULL) {
956 return (fmd_msg_nv_error("no such name-value pair "
957 "member: %s\n", t.t_data.tu_str));
958 }
959
960 return (fmd_msg_nv_parse_nvpair(b, nvp, s2));
961 }
962
963 /*
964 * Parse an expression rooted at an nvlist: if we see EOF, print nothing.
965 * If we see DOT, continue parsing to retrieve a name-value pair name.
966 */
967 static int
fmd_msg_nv_parse_nvlist(fmd_msg_buf_t * b,nvlist_t * nvl,char * s1)968 fmd_msg_nv_parse_nvlist(fmd_msg_buf_t *b, nvlist_t *nvl, char *s1)
969 {
970 fmd_msg_nv_token_t t;
971 char *s2;
972
973 s2 = fmd_msg_nv_parse_token(s1, &t);
974
975 if (t.t_kind == T_EOF)
976 return (0);
977
978 if (t.t_kind == T_DOT)
979 return (fmd_msg_nv_parse_nvname(b, nvl, s2));
980
981 return (fmd_msg_nv_parse_error(s1, &t));
982 }
983
984 /*
985 * This function is the main engine for formatting an event message item, such
986 * as the Description field. It loads the item text from a message object,
987 * expands any variables defined in the item text, and then returns a newly-
988 * allocated multi-byte string with the localized message text, or NULL with
989 * errno set if an error occurred.
990 */
991 static char *
fmd_msg_getitem_locked(fmd_msg_hdl_t * h,nvlist_t * nvl,const char * dict,const char * code,fmd_msg_item_t item)992 fmd_msg_getitem_locked(fmd_msg_hdl_t *h,
993 nvlist_t *nvl, const char *dict, const char *code, fmd_msg_item_t item)
994 {
995 const char *istr = fmd_msg_items[item];
996 size_t len = strlen(code) + 1 + strlen(istr) + 1;
997 char *key = alloca(len);
998
999 fmd_msg_buf_t buf;
1000 wchar_t *c, *u, *w, *p, *q;
1001
1002 const char *url, *txt;
1003 char *s, *expr;
1004 size_t elen;
1005 int i;
1006
1007 assert(fmd_msg_lock_held(h));
1008
1009 /*
1010 * If <dict>.mo defines an item with the key <FMD_MSG_URLKEY> then it
1011 * is used as the URL; otherwise the default from our handle is used.
1012 * Once we have the multi-byte URL, convert it to wide-character form.
1013 */
1014 if ((url = dgettext(dict, FMD_MSG_URLKEY)) == FMD_MSG_URLKEY)
1015 url = h->fmh_urlbase;
1016
1017 /*
1018 * If the item is FMD_MSG_ITEM_URL, then its value is directly computed
1019 * as the URL base concatenated with the code. Otherwise the item text
1020 * is derived by looking up the key <code>.<istr> in the dict object.
1021 * Once we're done, convert the 'txt' multi-byte to wide-character.
1022 */
1023 if (item == FMD_MSG_ITEM_URL) {
1024 len = strlen(url) + strlen(code) + 1;
1025 key = alloca(len);
1026 (void) snprintf(key, len, "%s%s", url, code);
1027 txt = key;
1028 } else {
1029 len = strlen(code) + 1 + strlen(istr) + 1;
1030 key = alloca(len);
1031 (void) snprintf(key, len, "%s.%s", code, istr);
1032 txt = dgettext(dict, key);
1033 }
1034
1035 c = fmd_msg_mbstowcs(code);
1036 u = fmd_msg_mbstowcs(url);
1037 w = fmd_msg_mbstowcs(txt);
1038
1039 if (c == NULL || u == NULL || w == NULL) {
1040 free(c);
1041 free(u);
1042 free(w);
1043 return (NULL);
1044 }
1045
1046 /*
1047 * Now expand any escape sequences in the string, storing the final
1048 * text in 'buf' in wide-character format, and then convert it back
1049 * to multi-byte for return. We expand the following sequences:
1050 *
1051 * %% - literal % character
1052 * %s - base URL for knowledge articles
1053 * %<x> - expression x in the current event, if any
1054 *
1055 * If an invalid sequence is present, it is elided so we can safely
1056 * reserve any future characters for other types of expansions.
1057 */
1058 fmd_msg_buf_init(&buf);
1059
1060 for (q = w, p = w; (p = wcschr(p, L'%')) != NULL; q = p) {
1061 if (p > q)
1062 fmd_msg_buf_write(&buf, q, (size_t)(p - q));
1063
1064 switch (p[1]) {
1065 case L'%':
1066 fmd_msg_buf_write(&buf, p, 1);
1067 p += 2;
1068 break;
1069
1070 case L's':
1071 fmd_msg_buf_write(&buf, u, wcslen(u));
1072 fmd_msg_buf_write(&buf, c, wcslen(c));
1073
1074 p += 2;
1075 break;
1076
1077 case L'<':
1078 q = p + 2;
1079 p = wcschr(p + 2, L'>');
1080
1081 if (p == NULL)
1082 goto eos;
1083
1084 /*
1085 * The expression in %< > must be an ASCII string: as
1086 * such allocate its length in bytes plus an extra
1087 * MB_CUR_MAX for slop if a multi-byte character is in
1088 * there, plus another byte for \0. Since we move a
1089 * byte at a time, any multi-byte chars will just be
1090 * silently overwritten and fail to parse, which is ok.
1091 */
1092 elen = (size_t)(p - q);
1093 expr = malloc(elen + MB_CUR_MAX + 1);
1094
1095 if (expr == NULL) {
1096 buf.fmb_error = ENOMEM;
1097 goto eos;
1098 }
1099
1100 for (i = 0; i < elen; i++)
1101 (void) wctomb(&expr[i], q[i]);
1102
1103 expr[i] = '\0';
1104
1105 if (nvl != NULL)
1106 (void) fmd_msg_nv_parse_nvname(&buf, nvl, expr);
1107 else
1108 fmd_msg_buf_printf(&buf, "%%<%s>", expr);
1109
1110 free(expr);
1111 p++;
1112 break;
1113
1114 case L'\0':
1115 goto eos;
1116
1117 default:
1118 p += 2;
1119 break;
1120 }
1121 }
1122 eos:
1123 fmd_msg_buf_write(&buf, q, wcslen(q) + 1);
1124
1125 free(c);
1126 free(u);
1127 free(w);
1128
1129 s = fmd_msg_buf_read(&buf);
1130 fmd_msg_buf_fini(&buf);
1131
1132 return (s);
1133 }
1134
1135 /*
1136 * This is a private interface used by the notification daemons to parse tokens
1137 * in user-supplied message templates.
1138 */
1139 char *
fmd_msg_decode_tokens(nvlist_t * nvl,const char * msg,const char * url)1140 fmd_msg_decode_tokens(nvlist_t *nvl, const char *msg, const char *url)
1141 {
1142 fmd_msg_buf_t buf;
1143 wchar_t *h, *u, *w, *p, *q;
1144
1145 char *s, *expr, host[MAXHOSTNAMELEN + 1];
1146 size_t elen;
1147 int i;
1148
1149 u = fmd_msg_mbstowcs(url);
1150
1151 (void) gethostname(host, MAXHOSTNAMELEN + 1);
1152 h = fmd_msg_mbstowcs(host);
1153
1154 if ((w = fmd_msg_mbstowcs(msg)) == NULL)
1155 return (NULL);
1156
1157 /*
1158 * Now expand any escape sequences in the string, storing the final
1159 * text in 'buf' in wide-character format, and then convert it back
1160 * to multi-byte for return. We expand the following sequences:
1161 *
1162 * %% - literal % character
1163 * %h - hostname
1164 * %s - base URL for knowledge articles
1165 * %<x> - expression x in the current event, if any
1166 *
1167 * If an invalid sequence is present, it is elided so we can safely
1168 * reserve any future characters for other types of expansions.
1169 */
1170 fmd_msg_buf_init(&buf);
1171
1172 for (q = w, p = w; (p = wcschr(p, L'%')) != NULL; q = p) {
1173 if (p > q)
1174 fmd_msg_buf_write(&buf, q, (size_t)(p - q));
1175
1176 switch (p[1]) {
1177 case L'%':
1178 fmd_msg_buf_write(&buf, p, 1);
1179 p += 2;
1180 break;
1181
1182 case L'h':
1183 if (h != NULL)
1184 fmd_msg_buf_write(&buf, h, wcslen(h));
1185
1186 p += 2;
1187 break;
1188
1189 case L's':
1190 if (u != NULL)
1191 fmd_msg_buf_write(&buf, u, wcslen(u));
1192
1193 p += 2;
1194 break;
1195
1196 case L'<':
1197 q = p + 2;
1198 p = wcschr(p + 2, L'>');
1199
1200 if (p == NULL)
1201 goto eos;
1202
1203 /*
1204 * The expression in %< > must be an ASCII string: as
1205 * such allocate its length in bytes plus an extra
1206 * MB_CUR_MAX for slop if a multi-byte character is in
1207 * there, plus another byte for \0. Since we move a
1208 * byte at a time, any multi-byte chars will just be
1209 * silently overwritten and fail to parse, which is ok.
1210 */
1211 elen = (size_t)(p - q);
1212 expr = malloc(elen + MB_CUR_MAX + 1);
1213
1214 if (expr == NULL) {
1215 buf.fmb_error = ENOMEM;
1216 goto eos;
1217 }
1218
1219 for (i = 0; i < elen; i++)
1220 (void) wctomb(&expr[i], q[i]);
1221
1222 expr[i] = '\0';
1223
1224 if (nvl != NULL)
1225 (void) fmd_msg_nv_parse_nvname(&buf, nvl, expr);
1226 else
1227 fmd_msg_buf_printf(&buf, "%%<%s>", expr);
1228
1229 free(expr);
1230 p++;
1231 break;
1232
1233 case L'\0':
1234 goto eos;
1235
1236 default:
1237 p += 2;
1238 break;
1239 }
1240 }
1241 eos:
1242 fmd_msg_buf_write(&buf, q, wcslen(q) + 1);
1243
1244 free(h);
1245 free(u);
1246 free(w);
1247
1248 s = fmd_msg_buf_read(&buf);
1249 fmd_msg_buf_fini(&buf);
1250
1251 return (s);
1252 }
1253
1254 /*
1255 * This function is the main engine for formatting an entire event message.
1256 * It retrieves the master format string for an event, formats the individual
1257 * items, and then produces the final string composing all of the items. The
1258 * result is a newly-allocated multi-byte string of the localized message
1259 * text, or NULL with errno set if an error occurred.
1260 */
1261 static char *
fmd_msg_gettext_locked(fmd_msg_hdl_t * h,nvlist_t * nvl,const char * dict,const char * code)1262 fmd_msg_gettext_locked(fmd_msg_hdl_t *h,
1263 nvlist_t *nvl, const char *dict, const char *code)
1264 {
1265 char *items[FMD_MSG_ITEM_MAX];
1266 const char *format;
1267 char *buf = NULL;
1268 size_t len;
1269 int i;
1270
1271 nvlist_t *fmri, *auth;
1272 struct tm tm, *tmp;
1273
1274 int64_t *tv;
1275 uint_t tn = 0;
1276 time_t sec;
1277 char date[64];
1278
1279 char *uuid, *src_name, *src_vers;
1280 char *platform, *server, *csn;
1281
1282 assert(fmd_msg_lock_held(h));
1283 bzero(items, sizeof (items));
1284
1285 for (i = 0; i < FMD_MSG_ITEM_MAX; i++) {
1286 items[i] = fmd_msg_getitem_locked(h, nvl, dict, code, i);
1287 if (items[i] == NULL)
1288 goto out;
1289 }
1290
1291 /*
1292 * If <dict>.mo defines an item with the key <FMD_MSG_TEMPLATE> then it
1293 * is used as the format; otherwise the default from FMD.mo is used.
1294 */
1295 if ((format = dgettext(dict, FMD_MSG_TEMPLATE)) == FMD_MSG_TEMPLATE)
1296 format = h->fmh_template;
1297
1298 if (nvlist_lookup_string(nvl, FM_SUSPECT_UUID, &uuid) != 0)
1299 uuid = (char *)FMD_MSG_MISSING;
1300
1301 if (nvlist_lookup_int64_array(nvl, FM_SUSPECT_DIAG_TIME,
1302 &tv, &tn) == 0 && tn == 2 && (sec = (time_t)tv[0]) != (time_t)-1 &&
1303 (tmp = localtime_r(&sec, &tm)) != NULL)
1304 (void) strftime(date, sizeof (date), "%a %b %e %H:%M:%S %Z %Y",
1305 tmp);
1306 else
1307 (void) strlcpy(date, FMD_MSG_MISSING, sizeof (date));
1308
1309 /*
1310 * Extract the relevant identifying elements of the FMRI and authority.
1311 * Note: for now, we ignore FM_FMRI_AUTH_DOMAIN (only for SPs).
1312 */
1313 if (nvlist_lookup_nvlist(nvl, FM_SUSPECT_DE, &fmri) != 0)
1314 fmri = NULL;
1315
1316 if (nvlist_lookup_nvlist(fmri, FM_FMRI_AUTHORITY, &auth) != 0)
1317 auth = NULL;
1318
1319 if (nvlist_lookup_string(fmri, FM_FMRI_FMD_NAME, &src_name) != 0)
1320 src_name = (char *)FMD_MSG_MISSING;
1321
1322 if (nvlist_lookup_string(fmri, FM_FMRI_FMD_VERSION, &src_vers) != 0)
1323 src_vers = (char *)FMD_MSG_MISSING;
1324
1325 if (nvlist_lookup_string(auth, FM_FMRI_AUTH_PRODUCT, &platform) != 0)
1326 platform = (char *)FMD_MSG_MISSING;
1327
1328 if (nvlist_lookup_string(auth, FM_FMRI_AUTH_SERVER, &server) != 0)
1329 server = (char *)FMD_MSG_MISSING;
1330
1331 if (nvlist_lookup_string(auth, FM_FMRI_AUTH_PRODUCT_SN, &csn) != 0 &&
1332 nvlist_lookup_string(auth, FM_FMRI_AUTH_CHASSIS, &csn) != 0)
1333 csn = (char *)FMD_MSG_MISSING;
1334
1335 /*
1336 * Format the message once to get its length, allocate a buffer, and
1337 * then format the message again into the buffer to return it.
1338 */
1339 len = snprintf(NULL, 0, format, code,
1340 items[FMD_MSG_ITEM_TYPE], items[FMD_MSG_ITEM_SEVERITY],
1341 date, platform, csn, server, src_name, src_vers, uuid,
1342 items[FMD_MSG_ITEM_DESC], items[FMD_MSG_ITEM_RESPONSE],
1343 items[FMD_MSG_ITEM_IMPACT], items[FMD_MSG_ITEM_ACTION]);
1344
1345 if ((buf = malloc(len + 1)) == NULL) {
1346 errno = ENOMEM;
1347 goto out;
1348 }
1349
1350 (void) snprintf(buf, len + 1, format, code,
1351 items[FMD_MSG_ITEM_TYPE], items[FMD_MSG_ITEM_SEVERITY],
1352 date, platform, csn, server, src_name, src_vers, uuid,
1353 items[FMD_MSG_ITEM_DESC], items[FMD_MSG_ITEM_RESPONSE],
1354 items[FMD_MSG_ITEM_IMPACT], items[FMD_MSG_ITEM_ACTION]);
1355 out:
1356 for (i = 0; i < FMD_MSG_ITEM_MAX; i++)
1357 free(items[i]);
1358
1359 return (buf);
1360 }
1361
1362 /*
1363 * Common code for fmd_msg_getitem_nv() and fmd_msg_getitem_id(): this function
1364 * handles locking, changing locales and domains, and restoring i18n state.
1365 */
1366 static char *
fmd_msg_getitem(fmd_msg_hdl_t * h,const char * locale,nvlist_t * nvl,const char * code,fmd_msg_item_t item)1367 fmd_msg_getitem(fmd_msg_hdl_t *h,
1368 const char *locale, nvlist_t *nvl, const char *code, fmd_msg_item_t item)
1369 {
1370 char *old_b, *old_c;
1371 char *dict, *key, *p, *s;
1372 size_t len;
1373 int err;
1374
1375 if ((p = strchr(code, '-')) == NULL || p == code) {
1376 errno = EINVAL;
1377 return (NULL);
1378 }
1379
1380 if (locale != NULL && strcmp(h->fmh_locale, locale) == 0)
1381 locale = NULL; /* simplify later tests */
1382
1383 dict = strndupa(code, p - code);
1384
1385 fmd_msg_lock();
1386
1387 /*
1388 * If a non-default text domain binding was requested, save the old
1389 * binding perform the re-bind now that fmd_msg_lock() is held.
1390 */
1391 if (h->fmh_binding != NULL) {
1392 p = bindtextdomain(dict, NULL);
1393 old_b = strdupa(p);
1394 (void) bindtextdomain(dict, h->fmh_binding);
1395 }
1396
1397 /*
1398 * Compute the lookup code for FMD_MSG_ITEM_TYPE: we'll use this to
1399 * determine if the dictionary contains any data for this code at all.
1400 */
1401 len = strlen(code) + 1 + strlen(fmd_msg_items[FMD_MSG_ITEM_TYPE]) + 1;
1402 key = alloca(len);
1403
1404 (void) snprintf(key, len, "%s.%s",
1405 code, fmd_msg_items[FMD_MSG_ITEM_TYPE]);
1406
1407 /*
1408 * Save the current locale string, and if we've been asked to fetch
1409 * the text for a different locale, switch locales now under the lock.
1410 */
1411 p = setlocale(LC_ALL, NULL);
1412 old_c = strdupa(p);
1413
1414 if (locale != NULL)
1415 (void) setlocale(LC_ALL, locale);
1416
1417 /*
1418 * Prefetch the first item: if this isn't found, and we're in a non-
1419 * default locale, attempt to fall back to the C locale for this code.
1420 */
1421 if (dgettext(dict, key) == key &&
1422 (locale != NULL || strcmp(h->fmh_locale, "C") != 0)) {
1423 (void) setlocale(LC_ALL, "C");
1424 locale = "C"; /* restore locale */
1425 }
1426
1427 if (dgettext(dict, key) == key) {
1428 s = NULL;
1429 err = ENOENT;
1430 } else {
1431 s = fmd_msg_getitem_locked(h, nvl, dict, code, item);
1432 err = errno;
1433 }
1434
1435 if (locale != NULL)
1436 (void) setlocale(LC_ALL, old_c);
1437
1438 if (h->fmh_binding != NULL)
1439 (void) bindtextdomain(dict, old_b);
1440
1441 fmd_msg_unlock();
1442
1443 if (s == NULL)
1444 errno = err;
1445
1446 return (s);
1447 }
1448
1449 char *
fmd_msg_getitem_nv(fmd_msg_hdl_t * h,const char * locale,nvlist_t * nvl,fmd_msg_item_t item)1450 fmd_msg_getitem_nv(fmd_msg_hdl_t *h,
1451 const char *locale, nvlist_t *nvl, fmd_msg_item_t item)
1452 {
1453 char *code;
1454
1455 if (item >= FMD_MSG_ITEM_MAX) {
1456 errno = EINVAL;
1457 return (NULL);
1458 }
1459
1460 if (nvlist_lookup_string(nvl, FM_SUSPECT_DIAG_CODE, &code) != 0) {
1461 errno = EINVAL;
1462 return (NULL);
1463 }
1464
1465 return (fmd_msg_getitem(h, locale, nvl, code, item));
1466 }
1467
1468 char *
fmd_msg_getitem_id(fmd_msg_hdl_t * h,const char * locale,const char * code,fmd_msg_item_t item)1469 fmd_msg_getitem_id(fmd_msg_hdl_t *h,
1470 const char *locale, const char *code, fmd_msg_item_t item)
1471 {
1472 if (item >= FMD_MSG_ITEM_MAX) {
1473 errno = EINVAL;
1474 return (NULL);
1475 }
1476
1477 return (fmd_msg_getitem(h, locale, NULL, code, item));
1478 }
1479
1480 char *
fmd_msg_gettext_key(fmd_msg_hdl_t * h,const char * locale,const char * dict,const char * key)1481 fmd_msg_gettext_key(fmd_msg_hdl_t *h,
1482 const char *locale, const char *dict, const char *key)
1483 {
1484 char *old_b, *old_c, *p, *s;
1485
1486 fmd_msg_lock();
1487
1488 /*
1489 * If a non-default text domain binding was requested, save the old
1490 * binding perform the re-bind now that fmd_msg_lock() is held.
1491 */
1492 if (h->fmh_binding != NULL) {
1493 p = bindtextdomain(dict, NULL);
1494 old_b = alloca(strlen(p) + 1);
1495 (void) strcpy(old_b, p);
1496 (void) bindtextdomain(dict, h->fmh_binding);
1497 }
1498
1499 /*
1500 * Save the current locale string, and if we've been asked to fetch
1501 * the text for a different locale, switch locales now under the lock.
1502 */
1503 p = setlocale(LC_ALL, NULL);
1504 old_c = alloca(strlen(p) + 1);
1505 (void) strcpy(old_c, p);
1506
1507 if (locale != NULL)
1508 (void) setlocale(LC_ALL, locale);
1509
1510 /*
1511 * First attempt to fetch the string in the current locale. If this
1512 * fails and we're in a non-default locale, attempt to fall back to the
1513 * C locale and try again. If it still fails then we return NULL and
1514 * set errno.
1515 */
1516 if ((s = dgettext(dict, key)) == key &&
1517 (locale != NULL || strcmp(h->fmh_locale, "C") != 0)) {
1518 (void) setlocale(LC_ALL, "C");
1519 locale = "C"; /* restore locale */
1520
1521 if ((s = dgettext(dict, key)) == key) {
1522 s = NULL;
1523 errno = ENOENT;
1524 }
1525 }
1526 if (locale != NULL)
1527 (void) setlocale(LC_ALL, old_c);
1528
1529 if (h->fmh_binding != NULL)
1530 (void) bindtextdomain(dict, old_b);
1531
1532 fmd_msg_unlock();
1533
1534 return (s);
1535 }
1536
1537 /*
1538 * Common code for fmd_msg_gettext_nv() and fmd_msg_gettext_id(): this function
1539 * handles locking, changing locales and domains, and restoring i18n state.
1540 */
1541 static char *
fmd_msg_gettext(fmd_msg_hdl_t * h,const char * locale,nvlist_t * nvl,const char * code)1542 fmd_msg_gettext(fmd_msg_hdl_t *h,
1543 const char *locale, nvlist_t *nvl, const char *code)
1544 {
1545 char *old_b, *old_c;
1546 char *dict, *key, *p, *s;
1547 size_t len;
1548 int err;
1549
1550 if ((p = strchr(code, '-')) == NULL || p == code) {
1551 errno = EINVAL;
1552 return (NULL);
1553 }
1554
1555 if (locale != NULL && strcmp(h->fmh_locale, locale) == 0)
1556 locale = NULL; /* simplify later tests */
1557
1558 dict = strndupa(code, p - code);
1559
1560 fmd_msg_lock();
1561
1562 /*
1563 * If a non-default text domain binding was requested, save the old
1564 * binding perform the re-bind now that fmd_msg_lock() is held.
1565 */
1566 if (h->fmh_binding != NULL) {
1567 p = bindtextdomain(dict, NULL);
1568 old_b = strdupa(p);
1569 (void) bindtextdomain(dict, h->fmh_binding);
1570 }
1571
1572 /*
1573 * Compute the lookup code for FMD_MSG_ITEM_TYPE: we'll use this to
1574 * determine if the dictionary contains any data for this code at all.
1575 */
1576 len = strlen(code) + 1 + strlen(fmd_msg_items[FMD_MSG_ITEM_TYPE]) + 1;
1577 key = alloca(len);
1578
1579 (void) snprintf(key, len, "%s.%s",
1580 code, fmd_msg_items[FMD_MSG_ITEM_TYPE]);
1581
1582 /*
1583 * Save the current locale string, and if we've been asked to fetch
1584 * the text for a different locale, switch locales now under the lock.
1585 */
1586 p = setlocale(LC_ALL, NULL);
1587 old_c = strdupa(p);
1588
1589 if (locale != NULL)
1590 (void) setlocale(LC_ALL, locale);
1591
1592 /*
1593 * Prefetch the first item: if this isn't found, and we're in a non-
1594 * default locale, attempt to fall back to the C locale for this code.
1595 */
1596 if (dgettext(dict, key) == key &&
1597 (locale != NULL || strcmp(h->fmh_locale, "C") != 0)) {
1598 (void) setlocale(LC_ALL, "C");
1599 locale = "C"; /* restore locale */
1600 }
1601
1602 if (dgettext(dict, key) == key) {
1603 s = NULL;
1604 err = ENOENT;
1605 } else {
1606 s = fmd_msg_gettext_locked(h, nvl, dict, code);
1607 err = errno;
1608 }
1609
1610 if (locale != NULL)
1611 (void) setlocale(LC_ALL, old_c);
1612
1613 if (h->fmh_binding != NULL)
1614 (void) bindtextdomain(dict, old_b);
1615
1616 fmd_msg_unlock();
1617
1618 if (s == NULL)
1619 errno = err;
1620
1621 return (s);
1622 }
1623
1624 char *
fmd_msg_gettext_nv(fmd_msg_hdl_t * h,const char * locale,nvlist_t * nvl)1625 fmd_msg_gettext_nv(fmd_msg_hdl_t *h, const char *locale, nvlist_t *nvl)
1626 {
1627 char *code;
1628
1629 if (nvlist_lookup_string(nvl, FM_SUSPECT_DIAG_CODE, &code) != 0) {
1630 errno = EINVAL;
1631 return (NULL);
1632 }
1633
1634 return (fmd_msg_gettext(h, locale, nvl, code));
1635 }
1636
1637 char *
fmd_msg_gettext_id(fmd_msg_hdl_t * h,const char * locale,const char * code)1638 fmd_msg_gettext_id(fmd_msg_hdl_t *h, const char *locale, const char *code)
1639 {
1640 return (fmd_msg_gettext(h, locale, NULL, code));
1641 }
1642