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