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