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