/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
*/
/*
* FMD Message Library
*
* This library supports a simple set of routines for use in converting FMA
* events and message codes to localized human-readable message strings.
*
* 1. Library API
*
* The APIs are as follows:
*
* fmd_msg_init - set up the library and return a handle
* fmd_msg_fini - destroy the handle from fmd_msg_init
*
* fmd_msg_locale_set - set the default locale (initially based on environ(5))
* fmd_msg_locale_get - get the default locale
*
* fmd_msg_url_set - set the default URL for knowledge articles
* fmd_msg_url_get - get the default URL for knowledge articles
*
* fmd_msg_gettext_nv - format the entire message for the given event
* fmd_msg_gettext_id - format the entire message for the given event code
* fmd_msg_gettext_key - format the entire message for the given dict for the
* given explicit message key
*
* fmd_msg_getitem_nv - format a single message item for the given event
* fmd_msg_getitem_id - format a single message item for the given event code
*
* Upon success, fmd_msg_gettext_* and fmd_msg_getitem_* return newly-allocated
* localized strings in multi-byte format. The caller must call free() on the
* resulting buffer to deallocate the string after making use of it. Upon
* failure, these functions return NULL and set errno as follows:
*
* ENOMEM - Memory allocation failure while formatting message
* ENOENT - No message was found for the specified message identifier
* EINVAL - Invalid argument (e.g. bad event code, illegal fmd_msg_item_t)
* EILSEQ - Illegal multi-byte sequence detected in message
*
* 2. Variable Expansion
*
* The human-readable messages are stored in msgfmt(1) message object files in
* the corresponding locale directories. The values for the message items are
* permitted to contain variable expansions, currently defined as follows:
*
* %% - literal % character
* %s - knowledge article URL (e.g. http://support.oracle.com/msg/<MSG-ID>)
* %< x > - value x from the current event, using the expression syntax below:
*
* foo.bar => print nvlist_t member "bar" contained within nvlist_t "foo"
* foo[123] => print array element 123 of nvlist_t member "foo"
* foo[123].bar => print member "bar" of nvlist_t element 123 in array "foo"
*
* For example, the msgstr value for FMD-8000-2K might be defined as:
*
* msgid "FMD-8000-2K.action"
* msgstr "Use fmdump -v -u %<uuid> to locate the module. Use fmadm \
* reset %<fault-list[0].asru.mod-name> to reset the module."
*
* 3. Locking
*
* In order to format a human-readable message, libfmd_msg must get and set
* the process locale and potentially alter text domain bindings. At present,
* these facilities in libc are not fully MT-safe. As such, a library-wide
* lock is provided: fmd_msg_lock() and fmd_msg_unlock(). These locking calls
* are made internally as part of the top-level library entry points, but they
* can also be used by applications that themselves call setlocale() and wish
* to appropriately synchronize with other threads that are calling libfmd_msg.
*/
#include <libintl.h>
#include <locale.h>
#include <wchar.h>
#include <alloca.h>
#include <assert.h>
#include <netdb.h>
#include <pthread.h>
#include <synch.h>
#include <strings.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <sys/sysmacros.h>
#include <fmd_msg.h>
struct fmd_msg_hdl {
};
typedef struct fmd_msg_buf {
static const char *const fmd_msg_items[] = {
"type", /* key for FMD_MSG_ITEM_TYPE */
"severity", /* key for FMD_MSG_ITEM_SEVERITY */
"description", /* key for FMD_MSG_ITEM_DESC */
"response", /* key for FMD_MSG_ITEM_RESPONSE */
"impact", /* key for FMD_MSG_ITEM_IMPACT */
"action", /* key for FMD_MSG_ITEM_ACTION */
"url", /* key for FMD_MSG_ITEM_URL */
};
/*
* An enumeration of token types. The following are valid tokens that can be
* embedded into the message content:
*
* T_INT - integer tokens (for array indices)
* T_IDENT - nvpair identifiers
* T_DOT - "."
* T_LBRAC - "["
* T_RBRAC - "]"
*
* A NULL character (T_EOF) is used to terminate messages.
* Invalid tokens are assigned the type T_ERR.
*/
typedef enum {
typedef struct fmd_msg_nv_token {
union {
} t_data;
static const struct fmd_msg_nv_type {
int (*nvt_value)();
int (*nvt_array)();
} fmd_msg_nv_types[] = {
sizeof (char *), nvpair_value_string, NULL },
sizeof (char *), NULL, nvpair_value_string_array },
};
/*ARGSUSED*/
static int
{
return (RW_WRITE_HELD(&fmd_msg_rwlock));
}
void
fmd_msg_lock(void)
{
if (pthread_rwlock_wrlock(&fmd_msg_rwlock) != 0)
abort();
}
void
fmd_msg_unlock(void)
{
if (pthread_rwlock_unlock(&fmd_msg_rwlock) != 0)
abort();
}
static fmd_msg_hdl_t *
{
fmd_msg_fini(h);
return (NULL);
}
{
fmd_msg_hdl_t *h = NULL;
const char *s;
if (version != FMD_MSG_VERSION)
return (fmd_msg_init_err(h, EINVAL));
return (fmd_msg_init_err(h, ENOMEM));
bzero(h, sizeof (fmd_msg_hdl_t));
h->fmh_version = version;
return (fmd_msg_init_err(h, ENOMEM));
/*
* Initialize the program's locale from the environment if it hasn't
* already been initialized, and then retrieve the default setting.
*/
if (h->fmh_locale == NULL)
return (fmd_msg_init_err(h, ENOMEM));
/*
* If a non-default root directory is specified, then look up the base
* directory for our default catalog, and set fmh_binding as the same
* directory prefixed with the new root directory. This simply turns
* environ(5) settings that can change the default messages binding.
*/
if (root[0] != '/')
return (fmd_msg_init_err(h, EINVAL));
return (fmd_msg_init_err(h, ENOMEM));
}
/*
* All FMA event dictionaries use msgfmt(1) message objects to produce
* messages, even for the C locale. We therefore want to use dgettext
* for all message lookups, but its defined behavior in the C locale is
* to return the input string. Since our input strings are event codes
* and not format strings, this doesn't help us. We resolve this nit
* by setting NLSPATH to a non-existent file: the presence of NLSPATH
* is defined to force dgettext(3C) to do a full lookup even for C.
*/
return (fmd_msg_init_err(h, errno));
/*
* Cache the message template for the current locale. This is the
* snprintf(3C) format string for the final human-readable message.
* If the lookup fails for the current locale, fall back to the C locale
* and try again. Then restore the original locale.
*/
}
return (h);
}
void
{
if (h == NULL)
return; /* simplify caller code */
free(h->fmh_binding);
free(h->fmh_urlbase);
free(h->fmh_locale);
free(h);
}
int
{
char *l;
return (-1);
}
return (-1);
}
fmd_msg_lock();
free(l);
return (-1);
}
free(h->fmh_locale);
h->fmh_locale = l;
return (0);
}
const char *
{
return (h->fmh_locale);
}
int
{
char *u;
return (-1);
}
return (-1);
}
fmd_msg_lock();
free(h->fmh_urlbase);
h->fmh_urlbase = u;
return (0);
}
const char *
{
return (h->fmh_urlbase);
}
static wchar_t *
fmd_msg_mbstowcs(const char *s)
{
if (w == NULL) {
return (NULL);
}
free(w);
return (NULL);
}
return (w);
}
static void
{
bzero(b, sizeof (fmd_msg_buf_t));
else
b->fmb_size = FMD_MSGBUF_SZ;
}
static void
{
bzero(b, sizeof (fmd_msg_buf_t));
}
static char *
{
char *s;
if (b->fmb_error != 0) {
return (NULL);
}
return (NULL);
}
free(s);
return (NULL);
}
return (s);
}
/*
* Buffer utility function to write a wide-character string into the buffer,
* appending it at the end, and growing the buffer as needed as we go. Any
* allocation errors are stored in fmb_error and deferred until later.
*/
static void
{
if (b->fmb_error == 0)
return;
}
}
b->fmb_used += n;
}
/*
* Buffer utility function to printf a multi-byte string, convert to wide-
* character form, and then write the result into an fmd_msg_buf_t.
*/
/*PRINTFLIKE2*/
static void
{
char *buf;
wchar_t *w;
if (b->fmb_error != 0)
} else {
fmd_msg_buf_write(b, w, wcslen(w));
free(w);
}
}
/*PRINTFLIKE1*/
static int
{
return (1);
return (1);
}
static const struct fmd_msg_nv_type *
{
const struct fmd_msg_nv_type *t;
break;
}
return (t);
}
/*
* Print the specified string, escaping any unprintable character sequences
* using the ISO C character escape sequences.
*/
static void
{
char c;
while ((c = *s++) != '\0') {
if (c >= ' ' && c <= '~' && c != '\'') {
fmd_msg_buf_printf(b, "%c", c);
continue;
}
switch (c) {
case '\0':
fmd_msg_buf_printf(b, "\\0");
break;
case '\a':
fmd_msg_buf_printf(b, "\\a");
break;
case '\b':
fmd_msg_buf_printf(b, "\\b");
break;
case '\f':
fmd_msg_buf_printf(b, "\\f");
break;
case '\n':
fmd_msg_buf_printf(b, "\\n");
break;
case '\r':
fmd_msg_buf_printf(b, "\\r");
break;
case '\t':
fmd_msg_buf_printf(b, "\\t");
break;
case '\v':
fmd_msg_buf_printf(b, "\\v");
break;
case '\'':
fmd_msg_buf_printf(b, "\\'");
break;
case '"':
fmd_msg_buf_printf(b, "\\\"");
break;
case '\\':
fmd_msg_buf_printf(b, "\\\\");
break;
default:
}
}
}
/*
* Print the value of the specified nvpair into the supplied buffer.
*
* For nvpairs that are arrays types, passing -1 as the idx param indicates
* that we want to print all of the elements in the array.
*
* Returns 0 on success, 1 otherwise.
*/
static int
{
uint_t i;
if (idx != -1u) {
if (idx >= n) {
return (fmd_msg_nv_error("index %u out-of-range for "
"array %s: valid range is [0 .. %u]\n",
}
n = 1;
}
if (i > 0)
switch (type) {
case DATA_TYPE_INT8:
break;
case DATA_TYPE_INT16:
break;
case DATA_TYPE_INT32:
break;
case DATA_TYPE_INT64:
break;
case DATA_TYPE_UINT8:
break;
case DATA_TYPE_UINT16:
break;
case DATA_TYPE_UINT32:
break;
case DATA_TYPE_UINT64:
break;
case DATA_TYPE_BYTE:
break;
case DATA_TYPE_BOOLEAN_VALUE:
break;
case DATA_TYPE_HRTIME:
break;
case DATA_TYPE_STRING:
fmd_msg_nv_print_string(b, *(char **)p);
break;
}
}
return (0);
}
/*
* Writes the value of the specified nvpair to the supplied buffer.
*
* Returns 0 on success, 1 otherwise.
*/
static int
{
uint64_t v;
void *a;
uint_t n;
int err;
fmd_msg_buf_printf(b, "true");
err = 0;
} else {
}
return (err);
}
/*
* Consume a token from the specified string, fill in the specified token
* struct, and return the new string position from which to continue parsing.
*/
static char *
{
char *p = s, *q, c = *s;
/*
* Skip whitespace and then look for an integer token first. We can't
* use isspace() or isdigit() because we're in setlocale() context now.
*/
while (c == ' ' || c == '\t' || c == '\v' || c == '\n' || c == '\r')
c = *++p;
if (c >= '0' && c <= '9') {
errno = 0;
if (errno != 0 || p == q) {
return (p);
}
return (q);
}
/*
* Look for a name-value pair identifier, which we define to be the
* regular expression [a-zA-Z_][a-zA-Z0-9_-]* (NOTE: Ideally "-" would
* not be allowed here and we would require ISO C identifiers, but many
* FMA event members use hyphens.) This code specifically cannot use
* the isspace(), isalnum() etc. macros because we are currently in the
* context of an earlier call to setlocale() that may have installed a
* non-C locale, but this code needs to always operate on C characters.
*/
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_') {
for (q = p + 1; (c = *q) != '\0'; q++) {
if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') &&
(c < '0' || c > '9') && (c != '_' && c != '-'))
break;
}
return (p);
}
return (q);
}
switch (c) {
case '\0':
return (p);
case '.':
return (p + 1);
case '[':
return (p + 1);
case ']':
return (p + 1);
default:
return (p);
}
}
static int
{
return (fmd_msg_nv_error("illegal character at \"%s\"\n", s));
else
return (fmd_msg_nv_error("syntax error near \"%s\"\n", s));
}
/*
* Parse an array expression for referencing an element of the specified
* nvpair_t, which is expected to be of an array type. If it's an array of
* intrinsics, print the specified value. If it's an array of nvlist_t's,
* call fmd_msg_nv_parse_nvlist() recursively to continue parsing.
*/
static int
{
uint_t i, n;
char *s2;
return (fmd_msg_nv_error("inappropriate use of operator [ ]: "
}
return (fmd_msg_nv_error("expected integer index after [\n"));
return (fmd_msg_nv_error("expected ] after [ %u\n", i));
/*
* An array of nvlist is different from other array types in that it
* permits us to continue parsing instead of printing a terminal node.
*/
if (i >= n) {
return (fmd_msg_nv_error("index %u out-of-range for "
"array %s: valid range is [0 .. %u]\n",
}
}
(void) fmd_msg_nv_parse_token(s2, &t);
return (fmd_msg_nv_error("expected end-of-string "
"in expression instead of \"%s\"\n", s2));
}
return (fmd_msg_nv_print_nvpair(b, nvp, i));
}
/*
* Parse an expression rooted at an nvpair_t. If we see EOF, print the entire
* nvpair. If we see LBRAC, parse an array expression. If we see DOT, call
* fmd_msg_nv_parse_nvname() recursively to dereference an embedded member.
*/
static int
{
char *s2;
return (fmd_msg_nv_parse_error(s1, &t));
return (fmd_msg_nv_error("inappropriate use of operator '.': "
}
}
/*
* Parse an expression for a name-value pair name (IDENT). If we find a match
* continue parsing with the corresponding nvpair_t.
*/
static int
{
char *s2;
return (fmd_msg_nv_parse_error(s1, &t));
break;
}
return (fmd_msg_nv_error("no such name-value pair "
}
}
/*
* Parse an expression rooted at an nvlist: if we see EOF, print nothing.
* If we see DOT, continue parsing to retrieve a name-value pair name.
*/
static int
{
char *s2;
return (0);
return (fmd_msg_nv_parse_error(s1, &t));
}
/*
* This function is the main engine for formatting an event message item, such
* as the Description field. It loads the item text from a message object,
* expands any variables defined in the item text, and then returns a newly-
* allocated multi-byte string with the localized message text, or NULL with
* errno set if an error occurred.
*/
static char *
{
int i;
assert(fmd_msg_lock_held(h));
&severity);
&proxied);
}
else
/*
* If <dict>.mo defines an item with the key <FMD_MSG_URLKEY> then it
* is used as the URL; otherwise the default from our handle is used.
* Once we have the multi-byte URL, convert it to wide-character form.
*/
url = h->fmh_urlbase;
/*
* If the item is FMD_MSG_ITEM_URL, then its value is directly
* computed as the URL base concatenated with the code. If the
* item is FMD_MSG_ITEM_SEVERITY then we try to use the severity
* found in the nvlist passed in. Otherwise the item text is
* derived by looking up the key <code>.<istr> in the dict object.
* Once we're done, convert the 'txt' multi-byte to wide-character.
*/
if (item == FMD_MSG_ITEM_URL) {
} else {
/*
* If the lookup fails then there is no local message content.
* Substitute the alternate message item instead.
*/
}
}
c = fmd_msg_mbstowcs(code);
u = fmd_msg_mbstowcs(url);
w = fmd_msg_mbstowcs(txt);
free(c);
free(u);
free(w);
return (NULL);
}
/*
* Now expand any escape sequences in the string, storing the final
* text in 'buf' in wide-character format, and then convert it back
* to multi-byte for return. We expand the following sequences:
*
* %% - literal % character
* %s - base URL for knowledge articles
* %S - base URL for alternate knowledge article
* %<x> - expression x in the current event, if any
*
* If an invalid sequence is present, it is elided so we can safely
* reserve any future characters for other types of expansions.
*/
if (p > q)
switch (p[1]) {
case L'%':
p += 2;
break;
case L's':
p += 2;
break;
case L'S':
p += 2;
break;
case L'<':
q = p + 2;
if (p == NULL)
goto eos;
/*
* The expression in %< > must be an ASCII string: as
* such allocate its length in bytes plus an extra
* MB_CUR_MAX for slop if a multi-byte character is in
* there, plus another byte for \0. Since we move a
* byte at a time, any multi-byte chars will just be
* silently overwritten and fail to parse, which is ok.
*/
goto eos;
}
for (i = 0; i < elen; i++)
expr[i] = '\0';
else
p++;
break;
case L'\0':
goto eos;
default:
p += 2;
break;
}
}
eos:
free(c);
free(u);
free(w);
s = fmd_msg_buf_read(&buf);
return (s);
}
/*
* This is a private interface used by the notification daemons to parse tokens
* in user-supplied message templates.
*/
char *
{
wchar_t *h, *u, *w, *p, *q;
int i;
u = fmd_msg_mbstowcs(url);
h = fmd_msg_mbstowcs(host);
return (NULL);
/*
* Now expand any escape sequences in the string, storing the final
* text in 'buf' in wide-character format, and then convert it back
* to multi-byte for return. We expand the following sequences:
*
* %% - literal % character
* %h - hostname
* %s - base URL for knowledge articles
* %<x> - expression x in the current event, if any
*
* If an invalid sequence is present, it is elided so we can safely
* reserve any future characters for other types of expansions.
*/
if (p > q)
switch (p[1]) {
case L'%':
p += 2;
break;
case L'h':
if (h != NULL)
p += 2;
break;
case L's':
if (u != NULL)
p += 2;
break;
case L'<':
q = p + 2;
if (p == NULL)
goto eos;
/*
* The expression in %< > must be an ASCII string: as
* such allocate its length in bytes plus an extra
* MB_CUR_MAX for slop if a multi-byte character is in
* there, plus another byte for \0. Since we move a
* byte at a time, any multi-byte chars will just be
* silently overwritten and fail to parse, which is ok.
*/
goto eos;
}
for (i = 0; i < elen; i++)
expr[i] = '\0';
else
p++;
break;
case L'\0':
goto eos;
default:
p += 2;
break;
}
}
eos:
free(h);
free(u);
free(w);
s = fmd_msg_buf_read(&buf);
return (s);
}
/*
* This function is the main engine for formatting an entire event message.
* It retrieves the master format string for an event, formats the individual
* items, and then produces the final string composing all of the items. The
* result is a newly-allocated multi-byte string of the localized message
* text, or NULL with errno set if an error occurred.
*/
static char *
{
const char *format;
int i;
assert(fmd_msg_lock_held(h));
for (i = 0; i < FMD_MSG_ITEM_MAX; i++) {
goto out;
}
/*
* If <dict>.mo defines an item with the key <FMD_MSG_TEMPLATE> then it
* is used as the format; otherwise the default from FMD.mo is used.
*/
format = h->fmh_template;
uuid = (char *)FMD_MSG_MISSING;
tmp);
else
/*
* Extract the relevant identifying elements of the FMRI and authority.
* Note: for now, we ignore FM_FMRI_AUTH_V0_DOMAIN_NAME (only for SPs).
*/
src_name = (char *)FMD_MSG_MISSING;
src_vers = (char *)FMD_MSG_MISSING;
&platform) != 0)
platform = (char *)FMD_MSG_MISSING;
server = (char *)FMD_MSG_MISSING;
csn = (char *)FMD_MSG_MISSING;
/*
* Format the message once to get its length, allocate a buffer, and
* then format the message again into the buffer to return it.
*/
goto out;
}
out:
for (i = 0; i < FMD_MSG_ITEM_MAX; i++)
return (buf);
}
/*
* Common code for fmd_msg_getitem_nv() and fmd_msg_getitem_id(): this function
* handles locking, changing locales and domains, and restoring i18n state.
*/
static char *
{
int err = 0;
return (NULL);
}
&proxied);
else
fmd_msg_lock();
/*
* If a non-default text domain binding was requested, save the old
* binding perform the re-bind now that fmd_msg_lock() is held.
*/
if (h->fmh_binding != NULL) {
}
/*
* Compute the lookup code for FMD_MSG_ITEM_TYPE: we'll use this to
* determine if the dictionary contains any data for this code at all.
*/
/*
* Save the current locale string, and if we've been asked to fetch
* the text for a different locale, switch locales now under the lock.
*/
/*
* Prefetch the first item: if this isn't found, and we're in a non-
* default locale, attempt to fall back to the C locale for this code.
*/
}
/*
* If the lookup fails again then there is no local message content.
* Check if the alternate message exists.
*/
s = NULL;
}
}
if (!err) {
}
if (h->fmh_binding != NULL)
if (s == NULL)
return (s);
}
char *
{
char *code;
if (item >= FMD_MSG_ITEM_MAX) {
return (NULL);
}
return (NULL);
}
}
char *
{
if (item >= FMD_MSG_ITEM_MAX) {
return (NULL);
}
}
char *
{
fmd_msg_lock();
/*
* If a non-default text domain binding was requested, save the old
* binding perform the re-bind now that fmd_msg_lock() is held.
*/
if (h->fmh_binding != NULL) {
}
/*
* Save the current locale string, and if we've been asked to fetch
* the text for a different locale, switch locales now under the lock.
*/
/*
* First attempt to fetch the string in the current locale. If this
* fails and we're in a non-default locale, attempt to fall back to the
* C locale and try again. If it still fails then we return NULL and
* set errno. We do not try to lookup the alternate (missing msg) msg
* here since this function is not used to lookup KA content.
*/
}
s = NULL;
}
if (h->fmh_binding != NULL)
return (s);
}
/*
* Common code for fmd_msg_gettext_nv() and fmd_msg_gettext_id(): this function
* handles locking, changing locales and domains, and restoring i18n state.
*/
static char *
{
int err = 0;
return (NULL);
}
&proxied);
else
fmd_msg_lock();
/*
* If a non-default text domain binding was requested, save the old
* binding perform the re-bind now that fmd_msg_lock() is held.
*/
if (h->fmh_binding != NULL) {
}
/*
* Compute the lookup code for FMD_MSG_ITEM_TYPE: we'll use this to
* determine if the dictionary contains any data for this code at all.
*/
/*
* Save the current locale string, and if we've been asked to fetch
* the text for a different locale, switch locales now under the lock.
*/
/*
* Prefetch the first item: if this isn't found, and we're in a non-
* default locale, attempt to fall back to the C locale for this code.
*/
}
/*
* If the lookup fails again then there is no local message content.
* Check if the alternate message exists.
*/
s = NULL;
}
}
if (!err) {
}
if (h->fmh_binding != NULL)
if (s == NULL)
return (s);
}
char *
{
char *code;
return (NULL);
}
}
char *
{
}