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