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