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