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