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