2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A
2N/A/*
2N/A * Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A
2N/A/*
2N/A * FMD Message Library
2N/A *
2N/A * This library supports a simple set of routines for use in converting FMA
2N/A * events and message codes to localized human-readable message strings.
2N/A *
2N/A * 1. Library API
2N/A *
2N/A * The APIs are as follows:
2N/A *
2N/A * fmd_msg_init - set up the library and return a handle
2N/A * fmd_msg_fini - destroy the handle from fmd_msg_init
2N/A *
2N/A * fmd_msg_locale_set - set the default locale (initially based on environ(5))
2N/A * fmd_msg_locale_get - get the default locale
2N/A *
2N/A * fmd_msg_url_set - set the default URL for knowledge articles
2N/A * fmd_msg_url_get - get the default URL for knowledge articles
2N/A *
2N/A * fmd_msg_gettext_nv - format the entire message for the given event
2N/A * fmd_msg_gettext_id - format the entire message for the given event code
2N/A * fmd_msg_gettext_key - format the entire message for the given dict for the
2N/A * given explicit message key
2N/A *
2N/A * fmd_msg_getitem_nv - format a single message item for the given event
2N/A * fmd_msg_getitem_id - format a single message item for the given event code
2N/A *
2N/A * Upon success, fmd_msg_gettext_* and fmd_msg_getitem_* return newly-allocated
2N/A * localized strings in multi-byte format. The caller must call free() on the
2N/A * resulting buffer to deallocate the string after making use of it. Upon
2N/A * failure, these functions return NULL and set errno as follows:
2N/A *
2N/A * ENOMEM - Memory allocation failure while formatting message
2N/A * ENOENT - No message was found for the specified message identifier
2N/A * EINVAL - Invalid argument (e.g. bad event code, illegal fmd_msg_item_t)
2N/A * EILSEQ - Illegal multi-byte sequence detected in message
2N/A *
2N/A * 2. Variable Expansion
2N/A *
2N/A * The human-readable messages are stored in msgfmt(1) message object files in
2N/A * the corresponding locale directories. The values for the message items are
2N/A * permitted to contain variable expansions, currently defined as follows:
2N/A *
2N/A * %% - literal % character
2N/A * %s - knowledge article URL (e.g. http://support.oracle.com/msg/<MSG-ID>)
2N/A * %< x > - value x from the current event, using the expression syntax below:
2N/A *
2N/A * foo.bar => print nvlist_t member "bar" contained within nvlist_t "foo"
2N/A * foo[123] => print array element 123 of nvlist_t member "foo"
2N/A * foo[123].bar => print member "bar" of nvlist_t element 123 in array "foo"
2N/A *
2N/A * For example, the msgstr value for FMD-8000-2K might be defined as:
2N/A *
2N/A * msgid "FMD-8000-2K.action"
2N/A * msgstr "Use fmdump -v -u %<uuid> to locate the module. Use fmadm \
2N/A * reset %<fault-list[0].asru.mod-name> to reset the module."
2N/A *
2N/A * 3. Locking
2N/A *
2N/A * In order to format a human-readable message, libfmd_msg must get and set
2N/A * the process locale and potentially alter text domain bindings. At present,
2N/A * these facilities in libc are not fully MT-safe. As such, a library-wide
2N/A * lock is provided: fmd_msg_lock() and fmd_msg_unlock(). These locking calls
2N/A * are made internally as part of the top-level library entry points, but they
2N/A * can also be used by applications that themselves call setlocale() and wish
2N/A * to appropriately synchronize with other threads that are calling libfmd_msg.
2N/A */
2N/A
2N/A
2N/A#include <sys/fm/protocol.h>
2N/A
2N/A#include <libintl.h>
2N/A#include <locale.h>
2N/A#include <wchar.h>
2N/A
2N/A#include <alloca.h>
2N/A#include <assert.h>
2N/A#include <netdb.h>
2N/A#include <pthread.h>
2N/A#include <synch.h>
2N/A#include <strings.h>
2N/A#include <stdarg.h>
2N/A#include <stdlib.h>
2N/A#include <stdio.h>
2N/A#include <errno.h>
2N/A#include <unistd.h>
2N/A#include <sys/sysmacros.h>
2N/A
2N/A#include <fmd_msg.h>
2N/A
2N/A#define FMD_MSGBUF_SZ 256
2N/A
2N/Astruct fmd_msg_hdl {
2N/A int fmh_version; /* libfmd_msg client abi version number */
2N/A char *fmh_urlbase; /* base url for all knowledge articles */
2N/A char *fmh_binding; /* base directory for bindtextdomain() */
2N/A char *fmh_locale; /* default program locale from environment */
2N/A const char *fmh_template; /* FMD_MSG_TEMPLATE value for fmh_locale */
2N/A};
2N/A
2N/Atypedef struct fmd_msg_buf {
2N/A wchar_t *fmb_data; /* wide-character data buffer */
2N/A size_t fmb_size; /* size of fmb_data in wchar_t units */
2N/A size_t fmb_used; /* used portion of fmb_data in wchar_t units */
2N/A int fmb_error; /* error if any has occurred */
2N/A} fmd_msg_buf_t;
2N/A
2N/Astatic const char *const fmd_msg_items[] = {
2N/A "type", /* key for FMD_MSG_ITEM_TYPE */
2N/A "severity", /* key for FMD_MSG_ITEM_SEVERITY */
2N/A "description", /* key for FMD_MSG_ITEM_DESC */
2N/A "response", /* key for FMD_MSG_ITEM_RESPONSE */
2N/A "impact", /* key for FMD_MSG_ITEM_IMPACT */
2N/A "action", /* key for FMD_MSG_ITEM_ACTION */
2N/A "url", /* key for FMD_MSG_ITEM_URL */
2N/A};
2N/A
2N/Astatic pthread_rwlock_t fmd_msg_rwlock = PTHREAD_RWLOCK_INITIALIZER;
2N/A
2N/Astatic const char FMD_MSG_DOMAIN[] = "FMD";
2N/Astatic const char FMD_MSG_TEMPLATE[] = "syslog-msgs-message-template";
2N/Astatic const char FMD_MSG_URLKEY[] = "syslog-url";
2N/Astatic const char FMD_MSG_URLBASE[] = FMD_MSG_URL_DEFAULT;
2N/Astatic const char FMD_MSG_NLSPATH[] = "NLSPATH=/usr/lib/fm/fmd/fmd.cat";
2N/Astatic const char FMD_MSG_MISSING[] = "-";
2N/A
2N/A/*
2N/A * An enumeration of token types. The following are valid tokens that can be
2N/A * embedded into the message content:
2N/A *
2N/A * T_INT - integer tokens (for array indices)
2N/A * T_IDENT - nvpair identifiers
2N/A * T_DOT - "."
2N/A * T_LBRAC - "["
2N/A * T_RBRAC - "]"
2N/A *
2N/A * A NULL character (T_EOF) is used to terminate messages.
2N/A * Invalid tokens are assigned the type T_ERR.
2N/A */
2N/Atypedef enum {
2N/A T_EOF,
2N/A T_ERR,
2N/A T_IDENT,
2N/A T_INT,
2N/A T_DOT,
2N/A T_LBRAC,
2N/A T_RBRAC
2N/A} fmd_msg_nv_tkind_t;
2N/A
2N/Atypedef struct fmd_msg_nv_token {
2N/A fmd_msg_nv_tkind_t t_kind;
2N/A union {
2N/A char tu_str[256];
2N/A uint_t tu_int;
2N/A } t_data;
2N/A} fmd_msg_nv_token_t;
2N/A
2N/Astatic const struct fmd_msg_nv_type {
2N/A data_type_t nvt_type;
2N/A data_type_t nvt_base;
2N/A size_t nvt_size;
2N/A int (*nvt_value)();
2N/A int (*nvt_array)();
2N/A} fmd_msg_nv_types[] = {
2N/A { DATA_TYPE_INT8, DATA_TYPE_INT8,
2N/A sizeof (int8_t), nvpair_value_int8, NULL },
2N/A { DATA_TYPE_INT16, DATA_TYPE_INT16,
2N/A sizeof (int16_t), nvpair_value_int16, NULL },
2N/A { DATA_TYPE_INT32, DATA_TYPE_INT32,
2N/A sizeof (int32_t), nvpair_value_int32, NULL },
2N/A { DATA_TYPE_INT64, DATA_TYPE_INT64,
2N/A sizeof (int64_t), nvpair_value_int64, NULL },
2N/A { DATA_TYPE_UINT8, DATA_TYPE_UINT8,
2N/A sizeof (uint8_t), nvpair_value_uint8, NULL },
2N/A { DATA_TYPE_UINT16, DATA_TYPE_UINT16,
2N/A sizeof (uint16_t), nvpair_value_uint16, NULL },
2N/A { DATA_TYPE_UINT32, DATA_TYPE_UINT32,
2N/A sizeof (uint32_t), nvpair_value_uint32, NULL },
2N/A { DATA_TYPE_UINT64, DATA_TYPE_UINT64,
2N/A sizeof (uint64_t), nvpair_value_uint64, NULL },
2N/A { DATA_TYPE_BYTE, DATA_TYPE_BYTE,
2N/A sizeof (uchar_t), nvpair_value_byte, NULL },
2N/A { DATA_TYPE_BOOLEAN, DATA_TYPE_BOOLEAN,
2N/A 0, NULL, NULL },
2N/A { DATA_TYPE_BOOLEAN_VALUE, DATA_TYPE_BOOLEAN_VALUE,
2N/A sizeof (boolean_t), nvpair_value_boolean_value, NULL },
2N/A { DATA_TYPE_HRTIME, DATA_TYPE_HRTIME,
2N/A sizeof (hrtime_t), nvpair_value_hrtime, NULL },
2N/A { DATA_TYPE_STRING, DATA_TYPE_STRING,
2N/A sizeof (char *), nvpair_value_string, NULL },
2N/A { DATA_TYPE_NVLIST, DATA_TYPE_NVLIST,
2N/A sizeof (nvlist_t *), nvpair_value_nvlist, NULL },
2N/A { DATA_TYPE_INT8_ARRAY, DATA_TYPE_INT8,
2N/A sizeof (int8_t), NULL, nvpair_value_int8_array },
2N/A { DATA_TYPE_INT16_ARRAY, DATA_TYPE_INT16,
2N/A sizeof (int16_t), NULL, nvpair_value_int16_array },
2N/A { DATA_TYPE_INT32_ARRAY, DATA_TYPE_INT32,
2N/A sizeof (int32_t), NULL, nvpair_value_int32_array },
2N/A { DATA_TYPE_INT64_ARRAY, DATA_TYPE_INT64,
2N/A sizeof (int64_t), NULL, nvpair_value_int64_array },
2N/A { DATA_TYPE_UINT8_ARRAY, DATA_TYPE_UINT8,
2N/A sizeof (uint8_t), NULL, nvpair_value_uint8_array },
2N/A { DATA_TYPE_UINT16_ARRAY, DATA_TYPE_UINT16,
2N/A sizeof (uint16_t), NULL, nvpair_value_uint16_array },
2N/A { DATA_TYPE_UINT32_ARRAY, DATA_TYPE_UINT32,
2N/A sizeof (uint32_t), NULL, nvpair_value_uint32_array },
2N/A { DATA_TYPE_UINT64_ARRAY, DATA_TYPE_UINT64,
2N/A sizeof (uint64_t), NULL, nvpair_value_uint64_array },
2N/A { DATA_TYPE_BYTE_ARRAY, DATA_TYPE_BYTE,
2N/A sizeof (uchar_t), NULL, nvpair_value_byte_array },
2N/A { DATA_TYPE_BOOLEAN_ARRAY, DATA_TYPE_BOOLEAN_VALUE,
2N/A sizeof (boolean_t), NULL, nvpair_value_boolean_array },
2N/A { DATA_TYPE_STRING_ARRAY, DATA_TYPE_STRING,
2N/A sizeof (char *), NULL, nvpair_value_string_array },
2N/A { DATA_TYPE_NVLIST_ARRAY, DATA_TYPE_NVLIST,
2N/A sizeof (nvlist_t *), NULL, nvpair_value_nvlist_array },
2N/A { DATA_TYPE_UNKNOWN, DATA_TYPE_UNKNOWN, 0, NULL, NULL }
2N/A};
2N/A
2N/Astatic int fmd_msg_nv_parse_nvpair(fmd_msg_buf_t *, nvpair_t *, char *);
2N/Astatic int fmd_msg_nv_parse_nvname(fmd_msg_buf_t *, nvlist_t *, char *);
2N/Astatic int fmd_msg_nv_parse_nvlist(fmd_msg_buf_t *, nvlist_t *, char *);
2N/A
2N/A/*ARGSUSED*/
2N/Astatic int
2N/Afmd_msg_lock_held(fmd_msg_hdl_t *h)
2N/A{
2N/A return (RW_WRITE_HELD(&fmd_msg_rwlock));
2N/A}
2N/A
2N/Avoid
2N/Afmd_msg_lock(void)
2N/A{
2N/A if (pthread_rwlock_wrlock(&fmd_msg_rwlock) != 0)
2N/A abort();
2N/A}
2N/A
2N/Avoid
2N/Afmd_msg_unlock(void)
2N/A{
2N/A if (pthread_rwlock_unlock(&fmd_msg_rwlock) != 0)
2N/A abort();
2N/A}
2N/A
2N/Astatic fmd_msg_hdl_t *
2N/Afmd_msg_init_err(fmd_msg_hdl_t *h, int err)
2N/A{
2N/A fmd_msg_fini(h);
2N/A errno = err;
2N/A return (NULL);
2N/A}
2N/A
2N/Afmd_msg_hdl_t *
2N/Afmd_msg_init(const char *root, int version)
2N/A{
2N/A fmd_msg_hdl_t *h = NULL;
2N/A const char *s;
2N/A size_t len;
2N/A
2N/A if (version != FMD_MSG_VERSION)
2N/A return (fmd_msg_init_err(h, EINVAL));
2N/A
2N/A if ((h = malloc(sizeof (fmd_msg_hdl_t))) == NULL)
2N/A return (fmd_msg_init_err(h, ENOMEM));
2N/A
2N/A bzero(h, sizeof (fmd_msg_hdl_t));
2N/A h->fmh_version = version;
2N/A
2N/A if ((h->fmh_urlbase = strdup(FMD_MSG_URLBASE)) == NULL)
2N/A return (fmd_msg_init_err(h, ENOMEM));
2N/A
2N/A /*
2N/A * Initialize the program's locale from the environment if it hasn't
2N/A * already been initialized, and then retrieve the default setting.
2N/A */
2N/A (void) setlocale(LC_ALL, "");
2N/A s = setlocale(LC_ALL, NULL);
2N/A h->fmh_locale = strdup(s ? s : "C");
2N/A
2N/A if (h->fmh_locale == NULL)
2N/A return (fmd_msg_init_err(h, ENOMEM));
2N/A
2N/A /*
2N/A * If a non-default root directory is specified, then look up the base
2N/A * directory for our default catalog, and set fmh_binding as the same
2N/A * directory prefixed with the new root directory. This simply turns
2N/A * usr/lib/locale into <rootdir>/usr/lib/locale, but handles all of the
2N/A * environ(5) settings that can change the default messages binding.
2N/A */
2N/A if (root != NULL && root[0] != '\0' && strcmp(root, "/") != 0) {
2N/A if (root[0] != '/')
2N/A return (fmd_msg_init_err(h, EINVAL));
2N/A
2N/A if ((s = bindtextdomain(FMD_MSG_DOMAIN, NULL)) == NULL)
2N/A s = "/usr/lib/locale"; /* substitute default */
2N/A
2N/A len = strlen(root) + strlen(s) + 1;
2N/A
2N/A if ((h->fmh_binding = malloc(len)) == NULL)
2N/A return (fmd_msg_init_err(h, ENOMEM));
2N/A
2N/A (void) snprintf(h->fmh_binding, len, "%s%s", root, s);
2N/A }
2N/A
2N/A /*
2N/A * All FMA event dictionaries use msgfmt(1) message objects to produce
2N/A * messages, even for the C locale. We therefore want to use dgettext
2N/A * for all message lookups, but its defined behavior in the C locale is
2N/A * to return the input string. Since our input strings are event codes
2N/A * and not format strings, this doesn't help us. We resolve this nit
2N/A * by setting NLSPATH to a non-existent file: the presence of NLSPATH
2N/A * is defined to force dgettext(3C) to do a full lookup even for C.
2N/A */
2N/A if (getenv("NLSPATH") == NULL &&
2N/A ((s = strdup(FMD_MSG_NLSPATH)) == NULL || putenv((char *)s) != 0))
2N/A return (fmd_msg_init_err(h, errno));
2N/A
2N/A /*
2N/A * Cache the message template for the current locale. This is the
2N/A * snprintf(3C) format string for the final human-readable message.
2N/A * If the lookup fails for the current locale, fall back to the C locale
2N/A * and try again. Then restore the original locale.
2N/A */
2N/A if ((h->fmh_template = dgettext(FMD_MSG_DOMAIN, FMD_MSG_TEMPLATE))
2N/A == FMD_MSG_TEMPLATE && strcmp(h->fmh_locale, "C") != 0) {
2N/A (void) setlocale(LC_ALL, "C");
2N/A h->fmh_template = dgettext(FMD_MSG_DOMAIN, FMD_MSG_TEMPLATE);
2N/A (void) setlocale(LC_ALL, h->fmh_locale);
2N/A }
2N/A
2N/A return (h);
2N/A}
2N/A
2N/Avoid
2N/Afmd_msg_fini(fmd_msg_hdl_t *h)
2N/A{
2N/A if (h == NULL)
2N/A return; /* simplify caller code */
2N/A
2N/A free(h->fmh_binding);
2N/A free(h->fmh_urlbase);
2N/A free(h->fmh_locale);
2N/A free(h);
2N/A}
2N/A
2N/Aint
2N/Afmd_msg_locale_set(fmd_msg_hdl_t *h, const char *locale)
2N/A{
2N/A char *l;
2N/A
2N/A if (locale == NULL) {
2N/A errno = EINVAL;
2N/A return (-1);
2N/A }
2N/A
2N/A if ((l = strdup(locale)) == NULL) {
2N/A errno = ENOMEM;
2N/A return (-1);
2N/A }
2N/A
2N/A fmd_msg_lock();
2N/A
2N/A if (setlocale(LC_ALL, l) == NULL) {
2N/A free(l);
2N/A errno = EINVAL;
2N/A fmd_msg_unlock();
2N/A return (-1);
2N/A }
2N/A
2N/A h->fmh_template = dgettext(FMD_MSG_DOMAIN, FMD_MSG_TEMPLATE);
2N/A free(h->fmh_locale);
2N/A h->fmh_locale = l;
2N/A
2N/A fmd_msg_unlock();
2N/A return (0);
2N/A}
2N/A
2N/Aconst char *
2N/Afmd_msg_locale_get(fmd_msg_hdl_t *h)
2N/A{
2N/A return (h->fmh_locale);
2N/A}
2N/A
2N/Aint
2N/Afmd_msg_url_set(fmd_msg_hdl_t *h, const char *url)
2N/A{
2N/A char *u;
2N/A
2N/A if (url == NULL) {
2N/A errno = EINVAL;
2N/A return (-1);
2N/A }
2N/A
2N/A if ((u = strdup(url)) == NULL) {
2N/A errno = ENOMEM;
2N/A return (-1);
2N/A }
2N/A
2N/A fmd_msg_lock();
2N/A
2N/A free(h->fmh_urlbase);
2N/A h->fmh_urlbase = u;
2N/A
2N/A fmd_msg_unlock();
2N/A return (0);
2N/A}
2N/A
2N/Aconst char *
2N/Afmd_msg_url_get(fmd_msg_hdl_t *h)
2N/A{
2N/A return (h->fmh_urlbase);
2N/A}
2N/A
2N/Astatic wchar_t *
2N/Afmd_msg_mbstowcs(const char *s)
2N/A{
2N/A size_t n = strlen(s) + 1;
2N/A wchar_t *w = malloc(n * sizeof (wchar_t));
2N/A
2N/A if (w == NULL) {
2N/A errno = ENOMEM;
2N/A return (NULL);
2N/A }
2N/A
2N/A if (mbstowcs(w, s, n) == (size_t)-1) {
2N/A free(w);
2N/A return (NULL);
2N/A }
2N/A
2N/A return (w);
2N/A}
2N/A
2N/Astatic void
2N/Afmd_msg_buf_init(fmd_msg_buf_t *b)
2N/A{
2N/A bzero(b, sizeof (fmd_msg_buf_t));
2N/A b->fmb_data = malloc(sizeof (wchar_t) * FMD_MSGBUF_SZ);
2N/A
2N/A if (b->fmb_data == NULL)
2N/A b->fmb_error = ENOMEM;
2N/A else
2N/A b->fmb_size = FMD_MSGBUF_SZ;
2N/A}
2N/A
2N/Astatic void
2N/Afmd_msg_buf_fini(fmd_msg_buf_t *b)
2N/A{
2N/A free(b->fmb_data);
2N/A bzero(b, sizeof (fmd_msg_buf_t));
2N/A}
2N/A
2N/Astatic char *
2N/Afmd_msg_buf_read(fmd_msg_buf_t *b)
2N/A{
2N/A char *s;
2N/A
2N/A if (b->fmb_error != 0) {
2N/A errno = b->fmb_error;
2N/A return (NULL);
2N/A }
2N/A
2N/A if ((s = malloc(b->fmb_used * MB_CUR_MAX)) == NULL) {
2N/A errno = ENOMEM;
2N/A return (NULL);
2N/A }
2N/A
2N/A if (wcstombs(s, b->fmb_data, b->fmb_used) == (size_t)-1) {
2N/A free(s);
2N/A return (NULL);
2N/A }
2N/A
2N/A return (s);
2N/A}
2N/A
2N/A/*
2N/A * Buffer utility function to write a wide-character string into the buffer,
2N/A * appending it at the end, and growing the buffer as needed as we go. Any
2N/A * allocation errors are stored in fmb_error and deferred until later.
2N/A */
2N/Astatic void
2N/Afmd_msg_buf_write(fmd_msg_buf_t *b, const wchar_t *w, size_t n)
2N/A{
2N/A if (b->fmb_used + n > b->fmb_size) {
2N/A size_t size = MAX(b->fmb_size * 2, b->fmb_used + n);
2N/A wchar_t *data = malloc(sizeof (wchar_t) * size);
2N/A
2N/A if (data == NULL) {
2N/A if (b->fmb_error == 0)
2N/A b->fmb_error = ENOMEM;
2N/A return;
2N/A }
2N/A
2N/A bcopy(b->fmb_data, data, b->fmb_used * sizeof (wchar_t));
2N/A free(b->fmb_data);
2N/A
2N/A b->fmb_data = data;
2N/A b->fmb_size = size;
2N/A }
2N/A
2N/A bcopy(w, &b->fmb_data[b->fmb_used], sizeof (wchar_t) * n);
2N/A b->fmb_used += n;
2N/A}
2N/A
2N/A/*
2N/A * Buffer utility function to printf a multi-byte string, convert to wide-
2N/A * character form, and then write the result into an fmd_msg_buf_t.
2N/A */
2N/A/*PRINTFLIKE2*/
2N/Astatic void
2N/Afmd_msg_buf_printf(fmd_msg_buf_t *b, const char *format, ...)
2N/A{
2N/A ssize_t len;
2N/A va_list ap;
2N/A char *buf;
2N/A wchar_t *w;
2N/A
2N/A va_start(ap, format);
2N/A len = vsnprintf(NULL, 0, format, ap);
2N/A buf = alloca(len + 1);
2N/A (void) vsnprintf(buf, len + 1, format, ap);
2N/A va_end(ap);
2N/A
2N/A if ((w = fmd_msg_mbstowcs(buf)) == NULL) {
2N/A if (b->fmb_error != 0)
2N/A b->fmb_error = errno;
2N/A } else {
2N/A fmd_msg_buf_write(b, w, wcslen(w));
2N/A free(w);
2N/A }
2N/A}
2N/A
2N/A/*PRINTFLIKE1*/
2N/Astatic int
2N/Afmd_msg_nv_error(const char *format, ...)
2N/A{
2N/A int err = errno;
2N/A va_list ap;
2N/A
2N/A if (getenv("FMD_MSG_DEBUG") == NULL)
2N/A return (1);
2N/A
2N/A (void) fprintf(stderr, "libfmd_msg DEBUG: ");
2N/A va_start(ap, format);
2N/A (void) vfprintf(stderr, format, ap);
2N/A va_end(ap);
2N/A
2N/A if (strchr(format, '\n') == NULL)
2N/A (void) fprintf(stderr, ": %s\n", strerror(err));
2N/A
2N/A return (1);
2N/A}
2N/A
2N/Astatic const struct fmd_msg_nv_type *
2N/Afmd_msg_nv_type_lookup(data_type_t type)
2N/A{
2N/A const struct fmd_msg_nv_type *t;
2N/A
2N/A for (t = fmd_msg_nv_types; t->nvt_type != DATA_TYPE_UNKNOWN; t++) {
2N/A if (t->nvt_type == type)
2N/A break;
2N/A }
2N/A
2N/A return (t);
2N/A}
2N/A
2N/A/*
2N/A * Print the specified string, escaping any unprintable character sequences
2N/A * using the ISO C character escape sequences.
2N/A */
2N/Astatic void
2N/Afmd_msg_nv_print_string(fmd_msg_buf_t *b, const char *s)
2N/A{
2N/A char c;
2N/A
2N/A while ((c = *s++) != '\0') {
2N/A if (c >= ' ' && c <= '~' && c != '\'') {
2N/A fmd_msg_buf_printf(b, "%c", c);
2N/A continue;
2N/A }
2N/A
2N/A switch (c) {
2N/A case '\0':
2N/A fmd_msg_buf_printf(b, "\\0");
2N/A break;
2N/A case '\a':
2N/A fmd_msg_buf_printf(b, "\\a");
2N/A break;
2N/A case '\b':
2N/A fmd_msg_buf_printf(b, "\\b");
2N/A break;
2N/A case '\f':
2N/A fmd_msg_buf_printf(b, "\\f");
2N/A break;
2N/A case '\n':
2N/A fmd_msg_buf_printf(b, "\\n");
2N/A break;
2N/A case '\r':
2N/A fmd_msg_buf_printf(b, "\\r");
2N/A break;
2N/A case '\t':
2N/A fmd_msg_buf_printf(b, "\\t");
2N/A break;
2N/A case '\v':
2N/A fmd_msg_buf_printf(b, "\\v");
2N/A break;
2N/A case '\'':
2N/A fmd_msg_buf_printf(b, "\\'");
2N/A break;
2N/A case '"':
2N/A fmd_msg_buf_printf(b, "\\\"");
2N/A break;
2N/A case '\\':
2N/A fmd_msg_buf_printf(b, "\\\\");
2N/A break;
2N/A default:
2N/A fmd_msg_buf_printf(b, "\\x%02x", (uchar_t)c);
2N/A }
2N/A }
2N/A}
2N/A
2N/A/*
2N/A * Print the value of the specified nvpair into the supplied buffer.
2N/A *
2N/A * For nvpairs that are arrays types, passing -1 as the idx param indicates
2N/A * that we want to print all of the elements in the array.
2N/A *
2N/A * Returns 0 on success, 1 otherwise.
2N/A */
2N/Astatic int
2N/Afmd_msg_nv_print_items(fmd_msg_buf_t *b, nvpair_t *nvp,
2N/A data_type_t type, void *p, uint_t n, uint_t idx)
2N/A{
2N/A const struct fmd_msg_nv_type *nvt = fmd_msg_nv_type_lookup(type);
2N/A uint_t i;
2N/A
2N/A if (idx != -1u) {
2N/A if (idx >= n) {
2N/A return (fmd_msg_nv_error("index %u out-of-range for "
2N/A "array %s: valid range is [0 .. %u]\n",
2N/A idx, nvpair_name(nvp), n ? n - 1 : 0));
2N/A }
2N/A p = (uchar_t *)p + nvt->nvt_size * idx;
2N/A n = 1;
2N/A }
2N/A
2N/A for (i = 0; i < n; i++, p = (uchar_t *)p + nvt->nvt_size) {
2N/A if (i > 0)
2N/A fmd_msg_buf_printf(b, " "); /* array item delimiter */
2N/A
2N/A switch (type) {
2N/A case DATA_TYPE_INT8:
2N/A fmd_msg_buf_printf(b, "%d", *(int8_t *)p);
2N/A break;
2N/A
2N/A case DATA_TYPE_INT16:
2N/A fmd_msg_buf_printf(b, "%d", *(int16_t *)p);
2N/A break;
2N/A
2N/A case DATA_TYPE_INT32:
2N/A fmd_msg_buf_printf(b, "%d", *(int32_t *)p);
2N/A break;
2N/A
2N/A case DATA_TYPE_INT64:
2N/A fmd_msg_buf_printf(b, "%lld", *(longlong_t *)p);
2N/A break;
2N/A
2N/A case DATA_TYPE_UINT8:
2N/A fmd_msg_buf_printf(b, "%u", *(uint8_t *)p);
2N/A break;
2N/A
2N/A case DATA_TYPE_UINT16:
2N/A fmd_msg_buf_printf(b, "%u", *(uint16_t *)p);
2N/A break;
2N/A
2N/A case DATA_TYPE_UINT32:
2N/A fmd_msg_buf_printf(b, "%u", *(uint32_t *)p);
2N/A break;
2N/A
2N/A case DATA_TYPE_UINT64:
2N/A fmd_msg_buf_printf(b, "%llu", *(u_longlong_t *)p);
2N/A break;
2N/A
2N/A case DATA_TYPE_BYTE:
2N/A fmd_msg_buf_printf(b, "0x%x", *(uchar_t *)p);
2N/A break;
2N/A
2N/A case DATA_TYPE_BOOLEAN_VALUE:
2N/A fmd_msg_buf_printf(b,
2N/A *(boolean_t *)p ? "true" : "false");
2N/A break;
2N/A
2N/A case DATA_TYPE_HRTIME:
2N/A fmd_msg_buf_printf(b, "%lld", *(longlong_t *)p);
2N/A break;
2N/A
2N/A case DATA_TYPE_STRING:
2N/A fmd_msg_nv_print_string(b, *(char **)p);
2N/A break;
2N/A }
2N/A }
2N/A
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * Writes the value of the specified nvpair to the supplied buffer.
2N/A *
2N/A * Returns 0 on success, 1 otherwise.
2N/A */
2N/Astatic int
2N/Afmd_msg_nv_print_nvpair(fmd_msg_buf_t *b, nvpair_t *nvp, uint_t idx)
2N/A{
2N/A data_type_t type = nvpair_type(nvp);
2N/A const struct fmd_msg_nv_type *nvt = fmd_msg_nv_type_lookup(type);
2N/A
2N/A uint64_t v;
2N/A void *a;
2N/A uint_t n;
2N/A int err;
2N/A
2N/A if (nvt->nvt_type == DATA_TYPE_BOOLEAN) {
2N/A fmd_msg_buf_printf(b, "true");
2N/A err = 0;
2N/A } else if (nvt->nvt_array != NULL) {
2N/A (void) nvt->nvt_array(nvp, &a, &n);
2N/A err = fmd_msg_nv_print_items(b, nvp, nvt->nvt_base, a, n, idx);
2N/A } else if (nvt->nvt_value != NULL) {
2N/A (void) nvt->nvt_value(nvp, &v);
2N/A err = fmd_msg_nv_print_items(b, nvp, nvt->nvt_base, &v, 1, idx);
2N/A } else {
2N/A err = fmd_msg_nv_error("unknown data type %u", type);
2N/A }
2N/A
2N/A return (err);
2N/A}
2N/A
2N/A/*
2N/A * Consume a token from the specified string, fill in the specified token
2N/A * struct, and return the new string position from which to continue parsing.
2N/A */
2N/Astatic char *
2N/Afmd_msg_nv_parse_token(char *s, fmd_msg_nv_token_t *tp)
2N/A{
2N/A char *p = s, *q, c = *s;
2N/A
2N/A /*
2N/A * Skip whitespace and then look for an integer token first. We can't
2N/A * use isspace() or isdigit() because we're in setlocale() context now.
2N/A */
2N/A while (c == ' ' || c == '\t' || c == '\v' || c == '\n' || c == '\r')
2N/A c = *++p;
2N/A
2N/A if (c >= '0' && c <= '9') {
2N/A errno = 0;
2N/A tp->t_data.tu_int = strtoul(p, &q, 0);
2N/A
2N/A if (errno != 0 || p == q) {
2N/A tp->t_kind = T_ERR;
2N/A return (p);
2N/A }
2N/A
2N/A tp->t_kind = T_INT;
2N/A return (q);
2N/A }
2N/A
2N/A /*
2N/A * Look for a name-value pair identifier, which we define to be the
2N/A * regular expression [a-zA-Z_][a-zA-Z0-9_-]* (NOTE: Ideally "-" would
2N/A * not be allowed here and we would require ISO C identifiers, but many
2N/A * FMA event members use hyphens.) This code specifically cannot use
2N/A * the isspace(), isalnum() etc. macros because we are currently in the
2N/A * context of an earlier call to setlocale() that may have installed a
2N/A * non-C locale, but this code needs to always operate on C characters.
2N/A */
2N/A if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_') {
2N/A for (q = p + 1; (c = *q) != '\0'; q++) {
2N/A if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') &&
2N/A (c < '0' || c > '9') && (c != '_' && c != '-'))
2N/A break;
2N/A }
2N/A
2N/A if (sizeof (tp->t_data.tu_str) <= (size_t)(q - p)) {
2N/A tp->t_kind = T_ERR;
2N/A return (p);
2N/A }
2N/A
2N/A bcopy(p, tp->t_data.tu_str, (size_t)(q - p));
2N/A tp->t_data.tu_str[(size_t)(q - p)] = '\0';
2N/A tp->t_kind = T_IDENT;
2N/A return (q);
2N/A }
2N/A
2N/A switch (c) {
2N/A case '\0':
2N/A tp->t_kind = T_EOF;
2N/A return (p);
2N/A case '.':
2N/A tp->t_kind = T_DOT;
2N/A return (p + 1);
2N/A case '[':
2N/A tp->t_kind = T_LBRAC;
2N/A return (p + 1);
2N/A case ']':
2N/A tp->t_kind = T_RBRAC;
2N/A return (p + 1);
2N/A default:
2N/A tp->t_kind = T_ERR;
2N/A return (p);
2N/A }
2N/A}
2N/A
2N/Astatic int
2N/Afmd_msg_nv_parse_error(const char *s, fmd_msg_nv_token_t *tp)
2N/A{
2N/A if (tp->t_kind == T_ERR)
2N/A return (fmd_msg_nv_error("illegal character at \"%s\"\n", s));
2N/A else
2N/A return (fmd_msg_nv_error("syntax error near \"%s\"\n", s));
2N/A}
2N/A
2N/A/*
2N/A * Parse an array expression for referencing an element of the specified
2N/A * nvpair_t, which is expected to be of an array type. If it's an array of
2N/A * intrinsics, print the specified value. If it's an array of nvlist_t's,
2N/A * call fmd_msg_nv_parse_nvlist() recursively to continue parsing.
2N/A */
2N/Astatic int
2N/Afmd_msg_nv_parse_array(fmd_msg_buf_t *b, nvpair_t *nvp, char *s1)
2N/A{
2N/A fmd_msg_nv_token_t t;
2N/A nvlist_t **nva;
2N/A uint_t i, n;
2N/A char *s2;
2N/A
2N/A if (fmd_msg_nv_type_lookup(nvpair_type(nvp))->nvt_array == NULL) {
2N/A return (fmd_msg_nv_error("inappropriate use of operator [ ]: "
2N/A "element '%s' is not an array\n", nvpair_name(nvp)));
2N/A }
2N/A
2N/A s2 = fmd_msg_nv_parse_token(s1, &t);
2N/A i = t.t_data.tu_int;
2N/A
2N/A if (t.t_kind != T_INT)
2N/A return (fmd_msg_nv_error("expected integer index after [\n"));
2N/A
2N/A s2 = fmd_msg_nv_parse_token(s2, &t);
2N/A
2N/A if (t.t_kind != T_RBRAC)
2N/A return (fmd_msg_nv_error("expected ] after [ %u\n", i));
2N/A
2N/A /*
2N/A * An array of nvlist is different from other array types in that it
2N/A * permits us to continue parsing instead of printing a terminal node.
2N/A */
2N/A if (nvpair_type(nvp) == DATA_TYPE_NVLIST_ARRAY) {
2N/A (void) nvpair_value_nvlist_array(nvp, &nva, &n);
2N/A
2N/A if (i >= n) {
2N/A return (fmd_msg_nv_error("index %u out-of-range for "
2N/A "array %s: valid range is [0 .. %u]\n",
2N/A i, nvpair_name(nvp), n ? n - 1 : 0));
2N/A }
2N/A
2N/A return (fmd_msg_nv_parse_nvlist(b, nva[i], s2));
2N/A }
2N/A
2N/A (void) fmd_msg_nv_parse_token(s2, &t);
2N/A
2N/A if (t.t_kind != T_EOF) {
2N/A return (fmd_msg_nv_error("expected end-of-string "
2N/A "in expression instead of \"%s\"\n", s2));
2N/A }
2N/A
2N/A return (fmd_msg_nv_print_nvpair(b, nvp, i));
2N/A}
2N/A
2N/A/*
2N/A * Parse an expression rooted at an nvpair_t. If we see EOF, print the entire
2N/A * nvpair. If we see LBRAC, parse an array expression. If we see DOT, call
2N/A * fmd_msg_nv_parse_nvname() recursively to dereference an embedded member.
2N/A */
2N/Astatic int
2N/Afmd_msg_nv_parse_nvpair(fmd_msg_buf_t *b, nvpair_t *nvp, char *s1)
2N/A{
2N/A fmd_msg_nv_token_t t;
2N/A nvlist_t *nvl;
2N/A char *s2;
2N/A
2N/A s2 = fmd_msg_nv_parse_token(s1, &t);
2N/A
2N/A if (t.t_kind == T_EOF)
2N/A return (fmd_msg_nv_print_nvpair(b, nvp, -1));
2N/A
2N/A if (t.t_kind == T_LBRAC)
2N/A return (fmd_msg_nv_parse_array(b, nvp, s2));
2N/A
2N/A if (t.t_kind != T_DOT)
2N/A return (fmd_msg_nv_parse_error(s1, &t));
2N/A
2N/A if (nvpair_type(nvp) != DATA_TYPE_NVLIST) {
2N/A return (fmd_msg_nv_error("inappropriate use of operator '.': "
2N/A "element '%s' is not of type nvlist\n", nvpair_name(nvp)));
2N/A }
2N/A
2N/A (void) nvpair_value_nvlist(nvp, &nvl);
2N/A return (fmd_msg_nv_parse_nvname(b, nvl, s2));
2N/A}
2N/A
2N/A/*
2N/A * Parse an expression for a name-value pair name (IDENT). If we find a match
2N/A * continue parsing with the corresponding nvpair_t.
2N/A */
2N/Astatic int
2N/Afmd_msg_nv_parse_nvname(fmd_msg_buf_t *b, nvlist_t *nvl, char *s1)
2N/A{
2N/A nvpair_t *nvp = NULL;
2N/A fmd_msg_nv_token_t t;
2N/A char *s2;
2N/A
2N/A s2 = fmd_msg_nv_parse_token(s1, &t);
2N/A
2N/A if (t.t_kind != T_IDENT)
2N/A return (fmd_msg_nv_parse_error(s1, &t));
2N/A
2N/A while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
2N/A if (strcmp(nvpair_name(nvp), t.t_data.tu_str) == 0)
2N/A break;
2N/A }
2N/A
2N/A if (nvp == NULL) {
2N/A return (fmd_msg_nv_error("no such name-value pair "
2N/A "member: %s\n", t.t_data.tu_str));
2N/A }
2N/A
2N/A return (fmd_msg_nv_parse_nvpair(b, nvp, s2));
2N/A}
2N/A
2N/A/*
2N/A * Parse an expression rooted at an nvlist: if we see EOF, print nothing.
2N/A * If we see DOT, continue parsing to retrieve a name-value pair name.
2N/A */
2N/Astatic int
2N/Afmd_msg_nv_parse_nvlist(fmd_msg_buf_t *b, nvlist_t *nvl, char *s1)
2N/A{
2N/A fmd_msg_nv_token_t t;
2N/A char *s2;
2N/A
2N/A s2 = fmd_msg_nv_parse_token(s1, &t);
2N/A
2N/A if (t.t_kind == T_EOF)
2N/A return (0);
2N/A
2N/A if (t.t_kind == T_DOT)
2N/A return (fmd_msg_nv_parse_nvname(b, nvl, s2));
2N/A
2N/A return (fmd_msg_nv_parse_error(s1, &t));
2N/A}
2N/A
2N/A/*
2N/A * This function is the main engine for formatting an event message item, such
2N/A * as the Description field. It loads the item text from a message object,
2N/A * expands any variables defined in the item text, and then returns a newly-
2N/A * allocated multi-byte string with the localized message text, or NULL with
2N/A * errno set if an error occurred.
2N/A */
2N/Astatic char *
2N/Afmd_msg_getitem_locked(fmd_msg_hdl_t *h,
2N/A nvlist_t *nvl, const char *dict, const char *code, fmd_msg_item_t item)
2N/A{
2N/A const char *istr = fmd_msg_items[item];
2N/A size_t len = strlen(code) + 1 + strlen(istr) + 1;
2N/A char *key = alloca(len);
2N/A
2N/A fmd_msg_buf_t buf;
2N/A wchar_t *c, *u, *w, *p, *q, *altc;
2N/A
2N/A const char *url, *txt;
2N/A char *s, *expr, *altcode, *severity = NULL;
2N/A size_t elen;
2N/A int i;
2N/A boolean_t proxied = B_FALSE;
2N/A
2N/A assert(fmd_msg_lock_held(h));
2N/A
2N/A if (nvl != NULL) {
2N/A (void) nvlist_lookup_string(nvl, FM_SUSPECT_SEVERITY,
2N/A &severity);
2N/A (void) nvlist_lookup_boolean_value(nvl, FM_SUSPECT_PROXIED,
2N/A &proxied);
2N/A }
2N/A if (proxied == B_TRUE)
2N/A altcode = FMD_MSG_CODE_NOPOMSG_PROXIED;
2N/A else
2N/A altcode = FMD_MSG_CODE_NOPOMSG;
2N/A
2N/A altc = fmd_msg_mbstowcs(altcode);
2N/A
2N/A /*
2N/A * If <dict>.mo defines an item with the key <FMD_MSG_URLKEY> then it
2N/A * is used as the URL; otherwise the default from our handle is used.
2N/A * Once we have the multi-byte URL, convert it to wide-character form.
2N/A */
2N/A if ((url = dgettext(dict, FMD_MSG_URLKEY)) == FMD_MSG_URLKEY)
2N/A url = h->fmh_urlbase;
2N/A
2N/A /*
2N/A * If the item is FMD_MSG_ITEM_URL, then its value is directly
2N/A * computed as the URL base concatenated with the code. If the
2N/A * item is FMD_MSG_ITEM_SEVERITY then we try to use the severity
2N/A * found in the nvlist passed in. Otherwise the item text is
2N/A * derived by looking up the key <code>.<istr> in the dict object.
2N/A * Once we're done, convert the 'txt' multi-byte to wide-character.
2N/A */
2N/A if (item == FMD_MSG_ITEM_URL) {
2N/A len = strlen(url) + strlen(code) + 1;
2N/A key = alloca(len);
2N/A (void) snprintf(key, len, "%s%s", url, code);
2N/A txt = key;
2N/A } else if ((item == FMD_MSG_ITEM_SEVERITY) && (severity != NULL)) {
2N/A txt = severity;
2N/A } else {
2N/A len = strlen(code) + 1 + strlen(istr) + 1;
2N/A key = alloca(len);
2N/A (void) snprintf(key, len, "%s.%s", code, istr);
2N/A txt = dgettext(dict, key);
2N/A /*
2N/A * If the lookup fails then there is no local message content.
2N/A * Substitute the alternate message item instead.
2N/A */
2N/A if (txt == key) {
2N/A len = strlen(altcode) + 1 + strlen(istr) + 1;
2N/A key = alloca(len);
2N/A (void) snprintf(key, len, "%s.%s", altcode, istr);
2N/A txt = dgettext(FMD_MSG_DOMAIN, key);
2N/A }
2N/A }
2N/A
2N/A c = fmd_msg_mbstowcs(code);
2N/A u = fmd_msg_mbstowcs(url);
2N/A w = fmd_msg_mbstowcs(txt);
2N/A
2N/A if (c == NULL || altc == NULL || u == NULL || w == NULL) {
2N/A free(c);
2N/A free(u);
2N/A free(w);
2N/A free(altc);
2N/A return (NULL);
2N/A }
2N/A
2N/A /*
2N/A * Now expand any escape sequences in the string, storing the final
2N/A * text in 'buf' in wide-character format, and then convert it back
2N/A * to multi-byte for return. We expand the following sequences:
2N/A *
2N/A * %% - literal % character
2N/A * %s - base URL for knowledge articles
2N/A * %S - base URL for alternate knowledge article
2N/A * %<x> - expression x in the current event, if any
2N/A *
2N/A * If an invalid sequence is present, it is elided so we can safely
2N/A * reserve any future characters for other types of expansions.
2N/A */
2N/A fmd_msg_buf_init(&buf);
2N/A
2N/A for (q = w, p = w; (p = wcschr(p, L'%')) != NULL; q = p) {
2N/A if (p > q)
2N/A fmd_msg_buf_write(&buf, q, (size_t)(p - q));
2N/A
2N/A switch (p[1]) {
2N/A case L'%':
2N/A fmd_msg_buf_write(&buf, p, 1);
2N/A p += 2;
2N/A break;
2N/A
2N/A case L's':
2N/A fmd_msg_buf_write(&buf, u, wcslen(u));
2N/A fmd_msg_buf_write(&buf, c, wcslen(c));
2N/A
2N/A p += 2;
2N/A break;
2N/A
2N/A case L'S':
2N/A fmd_msg_buf_write(&buf, u, wcslen(u));
2N/A fmd_msg_buf_write(&buf, altc, wcslen(altc));
2N/A
2N/A p += 2;
2N/A break;
2N/A
2N/A case L'<':
2N/A q = p + 2;
2N/A p = wcschr(p + 2, L'>');
2N/A
2N/A if (p == NULL)
2N/A goto eos;
2N/A
2N/A /*
2N/A * The expression in %< > must be an ASCII string: as
2N/A * such allocate its length in bytes plus an extra
2N/A * MB_CUR_MAX for slop if a multi-byte character is in
2N/A * there, plus another byte for \0. Since we move a
2N/A * byte at a time, any multi-byte chars will just be
2N/A * silently overwritten and fail to parse, which is ok.
2N/A */
2N/A elen = (size_t)(p - q);
2N/A expr = malloc(elen + MB_CUR_MAX + 1);
2N/A
2N/A if (expr == NULL) {
2N/A buf.fmb_error = ENOMEM;
2N/A goto eos;
2N/A }
2N/A
2N/A for (i = 0; i < elen; i++)
2N/A (void) wctomb(&expr[i], q[i]);
2N/A
2N/A expr[i] = '\0';
2N/A
2N/A if (nvl != NULL)
2N/A (void) fmd_msg_nv_parse_nvname(&buf, nvl, expr);
2N/A else
2N/A fmd_msg_buf_printf(&buf, "%%<%s>", expr);
2N/A
2N/A free(expr);
2N/A p++;
2N/A break;
2N/A
2N/A case L'\0':
2N/A goto eos;
2N/A
2N/A default:
2N/A p += 2;
2N/A break;
2N/A }
2N/A }
2N/Aeos:
2N/A fmd_msg_buf_write(&buf, q, wcslen(q) + 1);
2N/A
2N/A free(c);
2N/A free(altc);
2N/A free(u);
2N/A free(w);
2N/A
2N/A s = fmd_msg_buf_read(&buf);
2N/A fmd_msg_buf_fini(&buf);
2N/A
2N/A return (s);
2N/A}
2N/A
2N/A/*
2N/A * This is a private interface used by the notification daemons to parse tokens
2N/A * in user-supplied message templates.
2N/A */
2N/Achar *
2N/Afmd_msg_decode_tokens(nvlist_t *nvl, const char *msg, const char *url)
2N/A{
2N/A fmd_msg_buf_t buf;
2N/A wchar_t *h, *u, *w, *p, *q;
2N/A
2N/A char *s, *expr, host[MAXHOSTNAMELEN + 1];
2N/A size_t elen;
2N/A int i;
2N/A
2N/A u = fmd_msg_mbstowcs(url);
2N/A
2N/A (void) gethostname(host, MAXHOSTNAMELEN + 1);
2N/A h = fmd_msg_mbstowcs(host);
2N/A
2N/A if ((w = fmd_msg_mbstowcs(msg)) == NULL)
2N/A return (NULL);
2N/A
2N/A /*
2N/A * Now expand any escape sequences in the string, storing the final
2N/A * text in 'buf' in wide-character format, and then convert it back
2N/A * to multi-byte for return. We expand the following sequences:
2N/A *
2N/A * %% - literal % character
2N/A * %h - hostname
2N/A * %s - base URL for knowledge articles
2N/A * %<x> - expression x in the current event, if any
2N/A *
2N/A * If an invalid sequence is present, it is elided so we can safely
2N/A * reserve any future characters for other types of expansions.
2N/A */
2N/A fmd_msg_buf_init(&buf);
2N/A
2N/A for (q = w, p = w; (p = wcschr(p, L'%')) != NULL; q = p) {
2N/A if (p > q)
2N/A fmd_msg_buf_write(&buf, q, (size_t)(p - q));
2N/A
2N/A switch (p[1]) {
2N/A case L'%':
2N/A fmd_msg_buf_write(&buf, p, 1);
2N/A p += 2;
2N/A break;
2N/A
2N/A case L'h':
2N/A if (h != NULL)
2N/A fmd_msg_buf_write(&buf, h, wcslen(h));
2N/A
2N/A p += 2;
2N/A break;
2N/A
2N/A case L's':
2N/A if (u != NULL)
2N/A fmd_msg_buf_write(&buf, u, wcslen(u));
2N/A
2N/A p += 2;
2N/A break;
2N/A
2N/A case L'<':
2N/A q = p + 2;
2N/A p = wcschr(p + 2, L'>');
2N/A
2N/A if (p == NULL)
2N/A goto eos;
2N/A
2N/A /*
2N/A * The expression in %< > must be an ASCII string: as
2N/A * such allocate its length in bytes plus an extra
2N/A * MB_CUR_MAX for slop if a multi-byte character is in
2N/A * there, plus another byte for \0. Since we move a
2N/A * byte at a time, any multi-byte chars will just be
2N/A * silently overwritten and fail to parse, which is ok.
2N/A */
2N/A elen = (size_t)(p - q);
2N/A expr = malloc(elen + MB_CUR_MAX + 1);
2N/A
2N/A if (expr == NULL) {
2N/A buf.fmb_error = ENOMEM;
2N/A goto eos;
2N/A }
2N/A
2N/A for (i = 0; i < elen; i++)
2N/A (void) wctomb(&expr[i], q[i]);
2N/A
2N/A expr[i] = '\0';
2N/A
2N/A if (nvl != NULL)
2N/A (void) fmd_msg_nv_parse_nvname(&buf, nvl, expr);
2N/A else
2N/A fmd_msg_buf_printf(&buf, "%%<%s>", expr);
2N/A
2N/A free(expr);
2N/A p++;
2N/A break;
2N/A
2N/A case L'\0':
2N/A goto eos;
2N/A
2N/A default:
2N/A p += 2;
2N/A break;
2N/A }
2N/A }
2N/Aeos:
2N/A fmd_msg_buf_write(&buf, q, wcslen(q) + 1);
2N/A
2N/A free(h);
2N/A free(u);
2N/A free(w);
2N/A
2N/A s = fmd_msg_buf_read(&buf);
2N/A fmd_msg_buf_fini(&buf);
2N/A
2N/A return (s);
2N/A}
2N/A
2N/A/*
2N/A * This function is the main engine for formatting an entire event message.
2N/A * It retrieves the master format string for an event, formats the individual
2N/A * items, and then produces the final string composing all of the items. The
2N/A * result is a newly-allocated multi-byte string of the localized message
2N/A * text, or NULL with errno set if an error occurred.
2N/A */
2N/Astatic char *
2N/Afmd_msg_gettext_locked(fmd_msg_hdl_t *h,
2N/A nvlist_t *nvl, const char *dict, const char *code)
2N/A{
2N/A char *items[FMD_MSG_ITEM_MAX];
2N/A const char *format;
2N/A char *buf = NULL;
2N/A size_t len;
2N/A int i;
2N/A
2N/A nvlist_t *fmri, *auth;
2N/A struct tm tm, *tmp;
2N/A
2N/A int64_t *tv;
2N/A uint_t tn = 0;
2N/A time_t sec;
2N/A char date[64];
2N/A
2N/A char *uuid, *src_name, *src_vers;
2N/A char *platform, *server, *csn;
2N/A
2N/A assert(fmd_msg_lock_held(h));
2N/A bzero(items, sizeof (items));
2N/A
2N/A for (i = 0; i < FMD_MSG_ITEM_MAX; i++) {
2N/A items[i] = fmd_msg_getitem_locked(h, nvl, dict, code, i);
2N/A if (items[i] == NULL)
2N/A goto out;
2N/A }
2N/A
2N/A /*
2N/A * If <dict>.mo defines an item with the key <FMD_MSG_TEMPLATE> then it
2N/A * is used as the format; otherwise the default from FMD.mo is used.
2N/A */
2N/A if ((format = dgettext(dict, FMD_MSG_TEMPLATE)) == FMD_MSG_TEMPLATE)
2N/A format = h->fmh_template;
2N/A
2N/A if (nvlist_lookup_string(nvl, FM_SUSPECT_UUID, &uuid) != 0)
2N/A uuid = (char *)FMD_MSG_MISSING;
2N/A
2N/A if (nvlist_lookup_int64_array(nvl, FM_SUSPECT_DIAG_TIME,
2N/A &tv, &tn) == 0 && tn == 2 && (sec = (time_t)tv[0]) != (time_t)-1 &&
2N/A (tmp = localtime_r(&sec, &tm)) != NULL)
2N/A (void) strftime(date, sizeof (date), "%a %b %e %H:%M:%S %Z %Y",
2N/A tmp);
2N/A else
2N/A (void) strlcpy(date, FMD_MSG_MISSING, sizeof (date));
2N/A
2N/A /*
2N/A * Extract the relevant identifying elements of the FMRI and authority.
2N/A * Note: for now, we ignore FM_FMRI_AUTH_V0_DOMAIN_NAME (only for SPs).
2N/A */
2N/A if (nvlist_lookup_nvlist(nvl, FM_SUSPECT_DE, &fmri) != 0)
2N/A fmri = NULL;
2N/A
2N/A if (nvlist_lookup_nvlist(fmri, FM_FMRI_AUTHORITY, &auth) != 0)
2N/A auth = NULL;
2N/A
2N/A if (nvlist_lookup_string(fmri, FM_FMRI_FMD_NAME, &src_name) != 0)
2N/A src_name = (char *)FMD_MSG_MISSING;
2N/A
2N/A if (nvlist_lookup_string(fmri, FM_FMRI_FMD_VERSION, &src_vers) != 0)
2N/A src_vers = (char *)FMD_MSG_MISSING;
2N/A
2N/A if (nvlist_lookup_string(auth, FM_FMRI_AUTH_V1_SYS_COMP_NM,
2N/A &platform) != 0)
2N/A platform = (char *)FMD_MSG_MISSING;
2N/A
2N/A if (nvlist_lookup_string(auth, FM_FMRI_AUTH_V1_SERVER_NM, &server) != 0)
2N/A server = (char *)FMD_MSG_MISSING;
2N/A
2N/A if (nvlist_lookup_string(auth, FM_FMRI_AUTH_V1_SYS_COMP_SN, &csn) != 0)
2N/A csn = (char *)FMD_MSG_MISSING;
2N/A
2N/A /*
2N/A * Format the message once to get its length, allocate a buffer, and
2N/A * then format the message again into the buffer to return it.
2N/A */
2N/A len = snprintf(NULL, 0, format, code,
2N/A items[FMD_MSG_ITEM_TYPE], items[FMD_MSG_ITEM_SEVERITY],
2N/A date, platform, csn, server, src_name, src_vers, uuid,
2N/A items[FMD_MSG_ITEM_DESC], items[FMD_MSG_ITEM_RESPONSE],
2N/A items[FMD_MSG_ITEM_IMPACT], items[FMD_MSG_ITEM_ACTION]);
2N/A
2N/A if ((buf = malloc(len + 1)) == NULL) {
2N/A errno = ENOMEM;
2N/A goto out;
2N/A }
2N/A
2N/A (void) snprintf(buf, len + 1, format, code,
2N/A items[FMD_MSG_ITEM_TYPE], items[FMD_MSG_ITEM_SEVERITY],
2N/A date, platform, csn, server, src_name, src_vers, uuid,
2N/A items[FMD_MSG_ITEM_DESC], items[FMD_MSG_ITEM_RESPONSE],
2N/A items[FMD_MSG_ITEM_IMPACT], items[FMD_MSG_ITEM_ACTION]);
2N/Aout:
2N/A for (i = 0; i < FMD_MSG_ITEM_MAX; i++)
2N/A free(items[i]);
2N/A
2N/A return (buf);
2N/A}
2N/A
2N/A/*
2N/A * Common code for fmd_msg_getitem_nv() and fmd_msg_getitem_id(): this function
2N/A * handles locking, changing locales and domains, and restoring i18n state.
2N/A */
2N/Astatic char *
2N/Afmd_msg_getitem(fmd_msg_hdl_t *h,
2N/A const char *locale, nvlist_t *nvl, const char *code, fmd_msg_item_t item)
2N/A{
2N/A char *old_b, *old_c;
2N/A char *dict, *key, *p, *s, *altcode;
2N/A size_t len;
2N/A int err = 0;
2N/A boolean_t proxied = B_FALSE;
2N/A
2N/A if ((p = strchr(code, '-')) == NULL || p == code) {
2N/A errno = EINVAL;
2N/A return (NULL);
2N/A }
2N/A
2N/A if (locale != NULL && strcmp(h->fmh_locale, locale) == 0)
2N/A locale = NULL; /* simplify later tests */
2N/A
2N/A dict = strndupa(code, p - code);
2N/A
2N/A if (nvl != NULL)
2N/A (void) nvlist_lookup_boolean_value(nvl, FM_SUSPECT_PROXIED,
2N/A &proxied);
2N/A if (proxied == B_TRUE)
2N/A altcode = FMD_MSG_CODE_NOPOMSG_PROXIED;
2N/A else
2N/A altcode = FMD_MSG_CODE_NOPOMSG;
2N/A
2N/A fmd_msg_lock();
2N/A
2N/A /*
2N/A * If a non-default text domain binding was requested, save the old
2N/A * binding perform the re-bind now that fmd_msg_lock() is held.
2N/A */
2N/A if (h->fmh_binding != NULL) {
2N/A p = bindtextdomain(dict, NULL);
2N/A old_b = strdupa(p);
2N/A (void) bindtextdomain(dict, h->fmh_binding);
2N/A }
2N/A
2N/A /*
2N/A * Compute the lookup code for FMD_MSG_ITEM_TYPE: we'll use this to
2N/A * determine if the dictionary contains any data for this code at all.
2N/A */
2N/A len = strlen(code) + 1 + strlen(fmd_msg_items[FMD_MSG_ITEM_TYPE]) + 1;
2N/A key = alloca(len);
2N/A
2N/A (void) snprintf(key, len, "%s.%s",
2N/A code, fmd_msg_items[FMD_MSG_ITEM_TYPE]);
2N/A
2N/A /*
2N/A * Save the current locale string, and if we've been asked to fetch
2N/A * the text for a different locale, switch locales now under the lock.
2N/A */
2N/A p = setlocale(LC_ALL, NULL);
2N/A old_c = strdupa(p);
2N/A
2N/A if (locale != NULL)
2N/A (void) setlocale(LC_ALL, locale);
2N/A
2N/A /*
2N/A * Prefetch the first item: if this isn't found, and we're in a non-
2N/A * default locale, attempt to fall back to the C locale for this code.
2N/A */
2N/A if (dgettext(dict, key) == key &&
2N/A (locale != NULL || strcmp(h->fmh_locale, "C") != 0)) {
2N/A (void) setlocale(LC_ALL, "C");
2N/A locale = "C"; /* restore locale */
2N/A }
2N/A
2N/A /*
2N/A * If the lookup fails again then there is no local message content.
2N/A * Check if the alternate message exists.
2N/A */
2N/A if (dgettext(dict, key) == key) {
2N/A len = strlen(altcode) + 1 +
2N/A strlen(fmd_msg_items[FMD_MSG_ITEM_TYPE]) + 1;
2N/A key = alloca(len);
2N/A (void) snprintf(key, len, "%s.%s",
2N/A altcode, fmd_msg_items[FMD_MSG_ITEM_TYPE]);
2N/A if (dgettext(FMD_MSG_DOMAIN, key) == key) {
2N/A s = NULL;
2N/A err = ENOENT;
2N/A }
2N/A }
2N/A
2N/A if (!err) {
2N/A s = fmd_msg_getitem_locked(h, nvl, dict, code, item);
2N/A err = errno;
2N/A }
2N/A
2N/A if (locale != NULL)
2N/A (void) setlocale(LC_ALL, old_c);
2N/A
2N/A if (h->fmh_binding != NULL)
2N/A (void) bindtextdomain(dict, old_b);
2N/A
2N/A fmd_msg_unlock();
2N/A
2N/A if (s == NULL)
2N/A errno = err;
2N/A
2N/A return (s);
2N/A}
2N/A
2N/Achar *
2N/Afmd_msg_getitem_nv(fmd_msg_hdl_t *h,
2N/A const char *locale, nvlist_t *nvl, fmd_msg_item_t item)
2N/A{
2N/A char *code;
2N/A
2N/A if (item >= FMD_MSG_ITEM_MAX) {
2N/A errno = EINVAL;
2N/A return (NULL);
2N/A }
2N/A
2N/A if (nvlist_lookup_string(nvl, FM_SUSPECT_DIAG_CODE, &code) != 0) {
2N/A errno = EINVAL;
2N/A return (NULL);
2N/A }
2N/A
2N/A return (fmd_msg_getitem(h, locale, nvl, code, item));
2N/A}
2N/A
2N/Achar *
2N/Afmd_msg_getitem_id(fmd_msg_hdl_t *h,
2N/A const char *locale, const char *code, fmd_msg_item_t item)
2N/A{
2N/A if (item >= FMD_MSG_ITEM_MAX) {
2N/A errno = EINVAL;
2N/A return (NULL);
2N/A }
2N/A
2N/A return (fmd_msg_getitem(h, locale, NULL, code, item));
2N/A}
2N/A
2N/Achar *
2N/Afmd_msg_gettext_key(fmd_msg_hdl_t *h,
2N/A const char *locale, const char *dict, const char *key)
2N/A{
2N/A char *old_b, *old_c, *p, *s;
2N/A
2N/A fmd_msg_lock();
2N/A
2N/A /*
2N/A * If a non-default text domain binding was requested, save the old
2N/A * binding perform the re-bind now that fmd_msg_lock() is held.
2N/A */
2N/A if (h->fmh_binding != NULL) {
2N/A p = bindtextdomain(dict, NULL);
2N/A old_b = alloca(strlen(p) + 1);
2N/A (void) strcpy(old_b, p);
2N/A (void) bindtextdomain(dict, h->fmh_binding);
2N/A }
2N/A
2N/A /*
2N/A * Save the current locale string, and if we've been asked to fetch
2N/A * the text for a different locale, switch locales now under the lock.
2N/A */
2N/A p = setlocale(LC_ALL, NULL);
2N/A old_c = alloca(strlen(p) + 1);
2N/A (void) strcpy(old_c, p);
2N/A
2N/A if (locale != NULL)
2N/A (void) setlocale(LC_ALL, locale);
2N/A
2N/A /*
2N/A * First attempt to fetch the string in the current locale. If this
2N/A * fails and we're in a non-default locale, attempt to fall back to the
2N/A * C locale and try again. If it still fails then we return NULL and
2N/A * set errno. We do not try to lookup the alternate (missing msg) msg
2N/A * here since this function is not used to lookup KA content.
2N/A */
2N/A if (dgettext(dict, key) == key &&
2N/A (locale != NULL || strcmp(h->fmh_locale, "C") != 0)) {
2N/A (void) setlocale(LC_ALL, "C");
2N/A locale = "C"; /* restore locale */
2N/A
2N/A }
2N/A
2N/A if ((s = dgettext(dict, key)) == key) {
2N/A s = NULL;
2N/A errno = ENOENT;
2N/A }
2N/A
2N/A if (locale != NULL)
2N/A (void) setlocale(LC_ALL, old_c);
2N/A
2N/A if (h->fmh_binding != NULL)
2N/A (void) bindtextdomain(dict, old_b);
2N/A
2N/A fmd_msg_unlock();
2N/A
2N/A return (s);
2N/A}
2N/A
2N/A/*
2N/A * Common code for fmd_msg_gettext_nv() and fmd_msg_gettext_id(): this function
2N/A * handles locking, changing locales and domains, and restoring i18n state.
2N/A */
2N/Astatic char *
2N/Afmd_msg_gettext(fmd_msg_hdl_t *h,
2N/A const char *locale, nvlist_t *nvl, const char *code)
2N/A{
2N/A char *old_b, *old_c;
2N/A char *dict, *key, *p, *s, *altcode;
2N/A size_t len;
2N/A int err = 0;
2N/A boolean_t proxied = B_FALSE;
2N/A
2N/A if ((p = strchr(code, '-')) == NULL || p == code) {
2N/A errno = EINVAL;
2N/A return (NULL);
2N/A }
2N/A
2N/A if (locale != NULL && strcmp(h->fmh_locale, locale) == 0)
2N/A locale = NULL; /* simplify later tests */
2N/A
2N/A dict = strndupa(code, p - code);
2N/A
2N/A if (nvl != NULL)
2N/A (void) nvlist_lookup_boolean_value(nvl, FM_SUSPECT_PROXIED,
2N/A &proxied);
2N/A
2N/A if (proxied == B_TRUE)
2N/A altcode = FMD_MSG_CODE_NOPOMSG_PROXIED;
2N/A else
2N/A altcode = FMD_MSG_CODE_NOPOMSG;
2N/A
2N/A fmd_msg_lock();
2N/A
2N/A /*
2N/A * If a non-default text domain binding was requested, save the old
2N/A * binding perform the re-bind now that fmd_msg_lock() is held.
2N/A */
2N/A if (h->fmh_binding != NULL) {
2N/A p = bindtextdomain(dict, NULL);
2N/A old_b = strdupa(p);
2N/A (void) bindtextdomain(dict, h->fmh_binding);
2N/A }
2N/A
2N/A /*
2N/A * Compute the lookup code for FMD_MSG_ITEM_TYPE: we'll use this to
2N/A * determine if the dictionary contains any data for this code at all.
2N/A */
2N/A len = strlen(code) + 1 + strlen(fmd_msg_items[FMD_MSG_ITEM_TYPE]) + 1;
2N/A key = alloca(len);
2N/A
2N/A (void) snprintf(key, len, "%s.%s",
2N/A code, fmd_msg_items[FMD_MSG_ITEM_TYPE]);
2N/A
2N/A /*
2N/A * Save the current locale string, and if we've been asked to fetch
2N/A * the text for a different locale, switch locales now under the lock.
2N/A */
2N/A p = setlocale(LC_ALL, NULL);
2N/A old_c = strdupa(p);
2N/A
2N/A if (locale != NULL)
2N/A (void) setlocale(LC_ALL, locale);
2N/A
2N/A /*
2N/A * Prefetch the first item: if this isn't found, and we're in a non-
2N/A * default locale, attempt to fall back to the C locale for this code.
2N/A */
2N/A if (dgettext(dict, key) == key &&
2N/A (locale != NULL || strcmp(h->fmh_locale, "C") != 0)) {
2N/A (void) setlocale(LC_ALL, "C");
2N/A locale = "C"; /* restore locale */
2N/A }
2N/A
2N/A /*
2N/A * If the lookup fails again then there is no local message content.
2N/A * Check if the alternate message exists.
2N/A */
2N/A if (dgettext(dict, key) == key) {
2N/A len = strlen(altcode) + 1 +
2N/A strlen(fmd_msg_items[FMD_MSG_ITEM_TYPE]) + 1;
2N/A key = alloca(len);
2N/A (void) snprintf(key, len, "%s.%s",
2N/A altcode, fmd_msg_items[FMD_MSG_ITEM_TYPE]);
2N/A if (dgettext(FMD_MSG_DOMAIN, key) == key) {
2N/A s = NULL;
2N/A err = ENOENT;
2N/A }
2N/A }
2N/A
2N/A if (!err) {
2N/A s = fmd_msg_gettext_locked(h, nvl, dict, code);
2N/A err = errno;
2N/A }
2N/A
2N/A if (locale != NULL)
2N/A (void) setlocale(LC_ALL, old_c);
2N/A
2N/A if (h->fmh_binding != NULL)
2N/A (void) bindtextdomain(dict, old_b);
2N/A
2N/A fmd_msg_unlock();
2N/A
2N/A if (s == NULL)
2N/A errno = err;
2N/A
2N/A return (s);
2N/A}
2N/A
2N/Achar *
2N/Afmd_msg_gettext_nv(fmd_msg_hdl_t *h, const char *locale, nvlist_t *nvl)
2N/A{
2N/A char *code;
2N/A
2N/A if (nvlist_lookup_string(nvl, FM_SUSPECT_DIAG_CODE, &code) != 0) {
2N/A errno = EINVAL;
2N/A return (NULL);
2N/A }
2N/A
2N/A return (fmd_msg_gettext(h, locale, nvl, code));
2N/A}
2N/A
2N/Achar *
2N/Afmd_msg_gettext_id(fmd_msg_hdl_t *h, const char *locale, const char *code)
2N/A{
2N/A return (fmd_msg_gettext(h, locale, NULL, code));
2N/A}