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