changeset 9501:f84d3cc28d24

PSARC 2009/265 fmdump -m 6810965 port fmdump -m to ON 6802474 Port libfmd_msg to ON 6805723 libtopo: port fmtopo -m to ON
author Robert Johnston <Robert.Johnston@Sun.COM>
date Wed, 29 Apr 2009 08:32:53 -0700
parents 1c35c22e01f3
children 06fd0ba02971
files usr/src/cmd/fm/dicts/Makefile usr/src/cmd/fm/dicts/TEST.dict usr/src/cmd/fm/dicts/TEST.po usr/src/cmd/fm/fmadm/Makefile.com usr/src/cmd/fm/fmadm/common/faulty.c usr/src/cmd/fm/fmd/common/fmd_xprt.c usr/src/cmd/fm/fmdump/Makefile.com usr/src/cmd/fm/fmdump/common/asru.c usr/src/cmd/fm/fmdump/common/error.c usr/src/cmd/fm/fmdump/common/fault.c usr/src/cmd/fm/fmdump/common/fmdump.c usr/src/cmd/fm/fmdump/common/fmdump.h usr/src/cmd/fm/fmtopo/common/fmtopo.c usr/src/cmd/fm/modules/common/snmp-trapgen/Makefile usr/src/cmd/fm/modules/common/snmp-trapgen/snmp.c usr/src/cmd/fm/modules/common/syslog-msgs/syslog.c usr/src/lib/fm/libfmd_msg/Makefile usr/src/lib/fm/libfmd_msg/Makefile.com usr/src/lib/fm/libfmd_msg/common/fmd_msg.c usr/src/lib/fm/libfmd_msg/common/fmd_msg.h usr/src/lib/fm/libfmd_msg/common/fmd_msg_test.c usr/src/lib/fm/libfmd_msg/common/fmd_msg_test.out usr/src/lib/fm/libfmd_msg/common/mapfile-vers usr/src/pkgdefs/etc/exception_list_i386 usr/src/pkgdefs/etc/exception_list_sparc
diffstat 25 files changed, 2233 insertions(+), 699 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/cmd/fm/dicts/Makefile	Wed Apr 29 03:44:12 2009 -0700
+++ b/usr/src/cmd/fm/dicts/Makefile	Wed Apr 29 08:32:53 2009 -0700
@@ -35,7 +35,8 @@
 	ZFS \
 	SCA500 \
 	SCA1000 \
-	SENSOR
+	SENSOR \
+	TEST
 
 i386_DCNAMES = \
 	AMD \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/fm/dicts/TEST.dict	Wed Apr 29 08:32:53 2009 -0700
@@ -0,0 +1,28 @@
+#
+#
+# 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
+# or http://www.opensolaris.org/os/licensing.
+# 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
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+
+FMDICT: name=TEST version=1 maxkey=1
+
+fault.test01=0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/fm/dicts/TEST.po	Wed Apr 29 08:32:53 2009 -0700
@@ -0,0 +1,41 @@
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+# 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
+# or http://www.opensolaris.org/os/licensing.
+# 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
+#
+#
+# This is portable object file is used for testing libfmd_msg.  It
+# is not delivered in any package.
+#
+
+msgid "TEST-8000-08.type"
+msgstr "Defect"
+msgid "TEST-8000-08.severity"
+msgstr "Minor"
+msgid "TEST-8000-08.description"
+msgstr "This entry tests URL variable expansion - url = %s"
+msgid "TEST-8000-08.response"
+msgstr "This entry tests the percent character escape sequence: %%"
+msgid "TEST-8000-08.impact"
+msgstr "This entry tests variable expansion for event payload members: uuid = %<uuid>, de scheme = %<de.scheme>"
+msgid "TEST-8000-08.action"
+msgstr "Variable expansion for arrays: index = %<test_arr[1].index>"
+
--- a/usr/src/cmd/fm/fmadm/Makefile.com	Wed Apr 29 03:44:12 2009 -0700
+++ b/usr/src/cmd/fm/fmadm/Makefile.com	Wed Apr 29 08:32:53 2009 -0700
@@ -19,10 +19,9 @@
 # CDDL HEADER END
 #
 #
-# Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
-#ident	"%Z%%M%	%I%	%E% SMI"
 
 .KEEP_STATE:
 .SUFFIXES:
@@ -43,7 +42,7 @@
 $(NOT_RELEASE_BUILD)CPPFLAGS += -DDEBUG
 CPPFLAGS += -I. -I../common
 CFLAGS += $(CTF_FLAGS) $(CCVERBOSE) $(XSTRCONST)
-LDLIBS += -L$(ROOT)/usr/lib/fm -lfmd_adm
+LDLIBS += -L$(ROOT)/usr/lib/fm -lfmd_adm -lfmd_msg
 LDLIBS += -lnvpair -ltopo
 LDFLAGS += -R/usr/lib/fm
 LINTFLAGS += -mnu
--- a/usr/src/cmd/fm/fmadm/common/faulty.c	Wed Apr 29 03:44:12 2009 -0700
+++ b/usr/src/cmd/fm/fmadm/common/faulty.c	Wed Apr 29 08:32:53 2009 -0700
@@ -37,6 +37,7 @@
 #include <sys/fm/protocol.h>
 #include <fm/libtopo.h>
 #include <fm/fmd_adm.h>
+#include <fm/fmd_msg.h>
 #include <dlfcn.h>
 #include <sys/systeminfo.h>
 #include <sys/utsname.h>
@@ -49,7 +50,6 @@
 #define	offsetof(s, m)	((size_t)(&(((s*)0)->m)))
 
 /*
- * catalog_setup() must be called to setup support functions.
  * Fault records are added to catalog by calling add_fault_record_to_catalog()
  * records are stored in order of importance to the system.
  * If -g flag is set or not_suppressed is not set and the class fru, fault,
@@ -212,7 +212,6 @@
 	name_list_t *asru;
 	name_list_t *fru;
 	name_list_t *serial;
-	char *url;
 	uint8_t not_suppressed;
 } status_record_t;
 
@@ -240,8 +239,6 @@
 resource_list_t *status_fru_list;
 resource_list_t *status_asru_list;
 
-static char *locale;
-static char *nlspath;
 static int max_display;
 static int max_fault = 0;
 static topo_hdl_t *topo_handle;
@@ -249,6 +246,7 @@
 static host_id_list_t *host_list;
 static int n_server;
 static int opt_g;
+static fmd_msg_hdl_t *fmadm_msghdl = NULL; /* handle for libfmd_msg calls */
 
 static char *
 format_date(char *buf, size_t len, uint64_t sec)
@@ -324,95 +322,6 @@
 	return (rt);
 }
 
-static void
-catalog_setup(void)
-{
-	char *tp;
-	int pl;
-
-	/*
-	 * 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.
-	 */
-	nlspath = getenv("NLSPATH");
-	if (nlspath == NULL)
-		putenv("NLSPATH=/usr/lib/fm/fmd/fmd.cat");
-	else {
-		pl = strlen(nlspath) + sizeof ("NLSPATH=") + 1;
-		tp = malloc(pl);
-		(void) snprintf(tp, pl, "NLSPATH=%s", nlspath);
-		nlspath = tp;
-	}
-
-	locale = setlocale(LC_MESSAGES, "");
-}
-
-static char *
-get_dict_url(char *id)
-{
-	char *url = "http://sun.com/msg/";
-	int msz = sizeof (url) + strlen(id) + 1;
-	char *cp;
-
-	cp = malloc(msz);
-	(void) snprintf(cp, msz, "%s%s", url, id);
-	return (cp);
-}
-
-static char *
-get_dict_msg(char *id, char *idx, int unknown, int translate)
-{
-	char mbuf[128];
-	char *msg;
-	char dbuf[32];
-	char *p;
-	int restore_env = 0;
-	int restore_locale = 0;
-
-	p = strchr(id, '-');
-	if (p == NULL || p == id || (p - id) >= 32) {
-		msg = mbuf;
-	} else {
-		strncpy(dbuf, id, (size_t)(p - id));
-		dbuf[(size_t)(p - id)] = 0;
-
-		(void) snprintf(mbuf, sizeof (mbuf), "%s.%s", id, idx);
-		if (translate == 0 || nlspath == NULL) {
-			(void) setlocale(LC_MESSAGES, "C");
-			restore_locale = 1;
-		}
-		bindtextdomain("FMD", "/usr/lib/locale");
-		msg = dgettext(dbuf, mbuf);
-		if (msg == mbuf) {
-			(void) setlocale(LC_MESSAGES, "C");
-			restore_locale = 1;
-			msg = dgettext(dbuf, mbuf);
-		}
-		if (msg == mbuf) {
-			putenv("NLSPATH=/usr/lib/fm/fmd/fmd.cat");
-			restore_env = 1;
-			(void) setlocale(LC_MESSAGES, "C");
-			msg = dgettext(dbuf, mbuf);
-		}
-		if (restore_locale)
-			(void) setlocale(LC_MESSAGES, locale);
-		if (restore_env && nlspath)
-			putenv(nlspath);
-	}
-	if (msg == mbuf) {
-		if (unknown)
-			msg = "unknown";
-		else
-			msg = NULL;
-	}
-	return (msg);
-}
-
 /*
  * compare two fru strings which are made up of substrings seperated by '/'
  * return true if every substring is the same in the two strings, or if a
@@ -751,7 +660,7 @@
 static status_record_t *
 new_record_init(uurec_t *uurec_p, char *msgid, name_list_t *class,
     name_list_t *fru, name_list_t *asru, name_list_t *resource,
-    name_list_t *serial, const char *url, boolean_t not_suppressed,
+    name_list_t *serial, boolean_t not_suppressed,
     hostid_t *hostid)
 {
 	status_record_t *status_rec_p;
@@ -763,13 +672,14 @@
 	uurec_p->next = NULL;
 	uurec_p->prev = NULL;
 	uurec_p->asru = asru;
-	status_rec_p->severity = get_dict_msg(msgid, "severity", 1, 0);
+	if ((status_rec_p->severity = fmd_msg_getitem_id(fmadm_msghdl, NULL,
+	    msgid, FMD_MSG_ITEM_SEVERITY)) == NULL)
+		status_rec_p->severity = strdup("unknown");
 	status_rec_p->class = class;
 	status_rec_p->fru = fru;
 	status_rec_p->asru = asru;
 	status_rec_p->resource = resource;
 	status_rec_p->serial = serial;
-	status_rec_p->url = url ? strdup(url) : NULL;
 	status_rec_p->msgid = strdup(msgid);
 	status_rec_p->not_suppressed = not_suppressed;
 	return (status_rec_p);
@@ -928,13 +838,13 @@
 static void
 catalog_new_record(uurec_t *uurec_p, char *msgid, name_list_t *class,
     name_list_t *fru, name_list_t *asru, name_list_t *resource,
-    name_list_t *serial, const char *url, boolean_t not_suppressed,
+    name_list_t *serial, boolean_t not_suppressed,
     hostid_t *hostid)
 {
 	status_record_t *status_rec_p;
 
 	status_rec_p = new_record_init(uurec_p, msgid, class, fru, asru,
-	    resource, serial, url, not_suppressed, hostid);
+	    resource, serial, not_suppressed, hostid);
 	add_rec_list(status_rec_p, &status_rec_list);
 	if (status_rec_p->fru)
 		add_list(status_rec_p, status_rec_p->fru, &status_fru_list);
@@ -949,7 +859,7 @@
 static void
 catalog_merge_record(status_record_t *status_rec_p, uurec_t *uurec_p,
     name_list_t *asru, name_list_t *resource, name_list_t *serial,
-    const char *url, boolean_t not_suppressed)
+    boolean_t not_suppressed)
 {
 	uurec_t *uurec1_p;
 
@@ -969,8 +879,6 @@
 		uurec_p->prev = uurec1_p;
 		uurec1_p->next = uurec_p;
 	}
-	if (status_rec_p->url == NULL && url != NULL)
-		status_rec_p->url = strdup(url);
 	status_rec_p->not_suppressed |= not_suppressed;
 	uurec_p->asru = merge_name_list(&status_rec_p->asru, asru, 0);
 	(void) merge_name_list(&status_rec_p->resource, resource, 0);
@@ -1102,8 +1010,7 @@
 }
 
 static void
-add_fault_record_to_catalog(nvlist_t *nvl, uint64_t sec, char *uuid,
-    const char *url)
+add_fault_record_to_catalog(nvlist_t *nvl, uint64_t sec, char *uuid)
 {
 	char *msgid = "-";
 	uint_t i, size = 0;
@@ -1152,12 +1059,12 @@
 		status_rec_p = record_in_catalog(class, fru, msgid, host);
 	if (status_rec_p) {
 		catalog_merge_record(status_rec_p, uurec_p, asru, resource,
-		    serial, url, not_suppressed);
+		    serial, not_suppressed);
 		free_name_list(class);
 		free_name_list(fru);
 	} else {
 		catalog_new_record(uurec_p, msgid, class, fru, asru,
-		    resource, serial, url, not_suppressed, host);
+		    resource, serial, not_suppressed, host);
 	}
 }
 
@@ -1238,45 +1145,23 @@
 }
 
 static void
-print_dict_info(char *msgid, char *url)
+print_dict_info_line(char *msgid, fmd_msg_item_t what, const char *linehdr)
 {
-	const char *cp;
-	char *l_url;
-	char *buf;
-	int bufsz;
+	char *cp = fmd_msg_getitem_id(fmadm_msghdl, NULL, msgid, what);
 
-	cp = get_dict_msg(msgid, "description", 0, 1);
 	if (cp) {
-		if (url)
-			l_url = url;
-		else
-			l_url = get_dict_url(msgid);
-		bufsz = strlen(cp) + strlen(l_url) + 1;
-		buf = malloc(bufsz);
-		(void) snprintf(buf, bufsz, cp, l_url);
-		print_line(dgettext("FMD", "Description : "), buf);
-		free(buf);
-		if (!url)
-			free(l_url);
+		print_line(dgettext("FMD", linehdr), cp);
+		free(cp);
 	}
-	cp = get_dict_msg(msgid, "response", 0, 1);
-	if (cp) {
-		buf = strdup(cp);
-		print_line(dgettext("FMD", "Response    : "), buf);
-		free(buf);
-	}
-	cp = get_dict_msg(msgid, "impact", 0, 1);
-	if (cp) {
-		buf = strdup(cp);
-		print_line(dgettext("FMD", "Impact      : "), buf);
-		free(buf);
-	}
-	cp = get_dict_msg(msgid, "action", 0, 1);
-	if (cp) {
-		buf = strdup(cp);
-		print_line(dgettext("FMD", "Action      : "), buf);
-		free(buf);
-	}
+}
+
+static void
+print_dict_info(char *msgid)
+{
+	print_dict_info_line(msgid, FMD_MSG_ITEM_DESC, "Description : ");
+	print_dict_info_line(msgid, FMD_MSG_ITEM_RESPONSE, "Response    : ");
+	print_dict_info_line(msgid, FMD_MSG_ITEM_IMPACT, "Impact      : ");
+	print_dict_info_line(msgid, FMD_MSG_ITEM_ACTION, "Action      : ");
 }
 
 static void
@@ -1598,7 +1483,7 @@
 		print_name_list(srp->serial, dgettext("FMD", "Serial ID.  :"),
 		    NULL, 0, 0, NULL, full);
 	}
-	print_dict_info(srp->msgid, srp->url);
+	print_dict_info(srp->msgid);
 	(void) printf("\n");
 }
 
@@ -1607,16 +1492,10 @@
 {
 	char buf[32];
 	uurec_t *uurp = srp->uurec;
-	char *severity;
 	static int header = 0;
 	char *head;
 	ari_list_t *ari_list;
 
-	if (nlspath)
-		severity = get_dict_msg(srp->msgid, "severity", 1, 1);
-	else
-		severity = srp->severity;
-
 	if (!summary || !header) {
 		if (opt_i) {
 			head = "--------------- "
@@ -1645,13 +1524,13 @@
 		while (ari_list) {
 			(void) printf("%-15s %-37s %-14s %-9s\n",
 			    format_date(buf, sizeof (buf), uurp->sec),
-			    ari_list->ari_uuid, srp->msgid, severity);
+			    ari_list->ari_uuid, srp->msgid, srp->severity);
 			ari_list = ari_list->next;
 		}
 	} else {
 		(void) printf("%-15s %-37s %-14s %-9s\n",
 		    format_date(buf, sizeof (buf), uurp->sec),
-		    uurp->uuid, srp->msgid, severity);
+		    uurp->uuid, srp->msgid, srp->severity);
 	}
 
 	if (!summary)
@@ -1820,8 +1699,7 @@
 					if (msgid == NULL ||
 					    strcmp(msgid, srp->msgid) != 0) {
 						msgid = srp->msgid;
-						print_dict_info(srp->msgid,
-						    srp->url);
+						print_dict_info(srp->msgid);
 					}
 					slp = slp->next;
 				} while (slp != end);
@@ -1918,7 +1796,7 @@
 		    &uuid);
 		if (uurecp == NULL || uuid_in_list(uuid, uurecp))
 			add_fault_record_to_catalog(acp->aci_event, *diag_time,
-			    uuid, acp->aci_url);
+			    uuid);
 	} else {
 		rt = -1;
 	}
@@ -1973,7 +1851,6 @@
 	uurec_select_t *tp;
 	uurec_select_t *uurecp = NULL;
 
-	catalog_setup();
 	while ((c = getopt(argc, argv, "afgin:prsu:v")) != EOF) {
 		switch (c) {
 		case 'a':
@@ -2017,6 +1894,8 @@
 	if (optind < argc)
 		return (FMADM_EXIT_USAGE);
 
+	if ((fmadm_msghdl = fmd_msg_init(NULL, FMD_MSG_VERSION)) == NULL)
+		return (FMADM_EXIT_ERROR);
 	rt = get_cases_from_fmd(adm, uurecp, opt_i);
 	if (opt_p) {
 		if ((pager = getenv("PAGER")) == NULL)
@@ -2038,6 +1917,7 @@
 		print_asru(opt_a);
 	if (opt_f == 0 && opt_r == 0)
 		print_catalog(opt_s, opt_a, opt_v, opt_i, opt_p && !opt_s);
+	fmd_msg_fini(fmadm_msghdl);
 	label_release_topo();
 	if (opt_p) {
 		(void) fclose(stdout);
--- a/usr/src/cmd/fm/fmd/common/fmd_xprt.c	Wed Apr 29 03:44:12 2009 -0700
+++ b/usr/src/cmd/fm/fmd/common/fmd_xprt.c	Wed Apr 29 08:32:53 2009 -0700
@@ -369,6 +369,7 @@
 fmd_xprt_transition(fmd_xprt_impl_t *xip,
     const fmd_xprt_rule_t *state, const char *tag)
 {
+	fmd_xprt_t *xp = (fmd_xprt_t *)xip;
 	fmd_event_t *e;
 	nvlist_t *nvl;
 	char *s;
@@ -391,6 +392,19 @@
 	if (state == _fmd_xprt_state_sub) {
 		fmd_xprt_subscribe_modhash(xip, fmd.d_mod_hash);
 
+		/*
+		 * For read-write transports, we always want to set up remote
+		 * subscriptions to the bultin list.* events, regardless of
+		 * whether any agents have subscribed to them.
+		 */
+		if (xip->xi_flags & FMD_XPRT_RDWR) {
+			fmd_xprt_subscribe(xp, FM_LIST_SUSPECT_CLASS);
+			fmd_xprt_subscribe(xp, FM_LIST_ISOLATED_CLASS);
+			fmd_xprt_subscribe(xp, FM_LIST_UPDATED_CLASS);
+			fmd_xprt_subscribe(xp, FM_LIST_RESOLVED_CLASS);
+			fmd_xprt_subscribe(xp, FM_LIST_REPAIRED_CLASS);
+		}
+
 		nvl = fmd_protocol_xprt_ctl(xip->xi_queue->eq_mod,
 		    "resource.fm.xprt.run", xip->xi_version);
 
--- a/usr/src/cmd/fm/fmdump/Makefile.com	Wed Apr 29 03:44:12 2009 -0700
+++ b/usr/src/cmd/fm/fmdump/Makefile.com	Wed Apr 29 08:32:53 2009 -0700
@@ -2,9 +2,8 @@
 # CDDL HEADER START
 #
 # The contents of this file are subject to the terms of the
-# Common Development and Distribution License, Version 1.0 only
-# (the "License").  You may not use this file except in compliance
-# with the License.
+# 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
 # or http://www.opensolaris.org/os/licensing.
@@ -20,10 +19,9 @@
 # CDDL HEADER END
 #
 #
-# Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
-#ident	"%Z%%M%	%I%	%E% SMI"
 
 .KEEP_STATE:
 .SUFFIXES:
@@ -38,7 +36,7 @@
 $(NOT_RELEASE_BUILD)CPPFLAGS += -DDEBUG
 CPPFLAGS += -I. -I../common -I../../include
 CFLAGS += $(CTF_FLAGS) $(CCVERBOSE) $(XSTRCONST)
-LDLIBS += -L$(ROOT)/usr/lib/fm -lfmd_log -lnvpair -ltopo
+LDLIBS += -L$(ROOT)/usr/lib/fm -lfmd_log -lnvpair -ltopo -lfmd_msg
 LDFLAGS += -R/usr/lib/fm
 LINTFLAGS += -mnu
 
--- a/usr/src/cmd/fm/fmdump/common/asru.c	Wed Apr 29 03:44:12 2009 -0700
+++ b/usr/src/cmd/fm/fmdump/common/asru.c	Wed Apr 29 08:32:53 2009 -0700
@@ -19,12 +19,10 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
-#pragma ident	"%Z%%M%	%I%	%E% SMI"
-
 #include <fmdump.h>
 #include <strings.h>
 #include <stdio.h>
@@ -93,5 +91,7 @@
 }, {
 "TIME                 UUID                                 STATE",
 (fmd_log_rec_f *)asru_verb2
+}, {
+NULL, NULL
 } }
 };
--- a/usr/src/cmd/fm/fmdump/common/error.c	Wed Apr 29 03:44:12 2009 -0700
+++ b/usr/src/cmd/fm/fmdump/common/error.c	Wed Apr 29 08:32:53 2009 -0700
@@ -19,12 +19,10 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
-#pragma ident	"%Z%%M%	%I%	%E% SMI"
-
 #include <fmdump.h>
 #include <stdio.h>
 #include <time.h>
@@ -81,5 +79,7 @@
 }, {
 "TIME                           CLASS",
 (fmd_log_rec_f *)err_verb2
+}, {
+NULL, NULL
 } }
 };
--- a/usr/src/cmd/fm/fmdump/common/fault.c	Wed Apr 29 03:44:12 2009 -0700
+++ b/usr/src/cmd/fm/fmdump/common/fault.c	Wed Apr 29 08:32:53 2009 -0700
@@ -19,12 +19,10 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
-#pragma ident	"%Z%%M%	%I%	%E% SMI"
-
 #include <fmdump.h>
 #include <stdio.h>
 #include <strings.h>
@@ -164,6 +162,51 @@
 	return (0);
 }
 
+/*
+ * There is a lack of uniformity in how the various entries in our diagnosis
+ * are terminated.  Some end with one newline, others with two.  This makes the
+ * output of fmdump -m look a bit ugly.  Therefore we postprocess the message
+ * before printing it, removing consecutive occurences of newlines.
+ */
+static void
+postprocess_msg(char *msg)
+{
+	int i = 0, j = 0;
+	char *buf;
+
+	if ((buf = malloc(strlen(msg) + 1)) == NULL)
+		return;
+
+	buf[j++] = msg[i++];
+	for (i = 1; i < strlen(msg); i++) {
+		if (!(msg[i] == '\n' && msg[i - 1] == '\n'))
+			buf[j++] = msg[i];
+	}
+	buf[j] = '\0';
+	(void) strncpy(msg, buf, j+1);
+	free(buf);
+}
+
+/*ARGSUSED*/
+static int
+flt_msg(fmd_log_t *lp, const fmd_log_record_t *rp, FILE *fp)
+{
+	char *msg;
+
+	if ((msg = fmd_msg_gettext_nv(g_msg, NULL, rp->rec_nvl)) == NULL) {
+		(void) fprintf(stderr, "%s: failed to format message: %s\n",
+		    g_pname, strerror(errno));
+		g_errs++;
+		return (-1);
+	} else {
+		postprocess_msg(msg);
+		fmdump_printf(fp, "%s\n", msg);
+		free(msg);
+	}
+
+	return (0);
+}
+
 const fmdump_ops_t fmdump_flt_ops = {
 "fault", {
 {
@@ -175,5 +218,8 @@
 }, {
 NULL,
 (fmd_log_rec_f *)flt_verb2
+}, {
+NULL,
+(fmd_log_rec_f *)flt_msg
 } }
 };
--- a/usr/src/cmd/fm/fmdump/common/fmdump.c	Wed Apr 29 03:44:12 2009 -0700
+++ b/usr/src/cmd/fm/fmdump/common/fmdump.c	Wed Apr 29 08:32:53 2009 -0700
@@ -47,7 +47,9 @@
 ulong_t g_errs;
 ulong_t g_recs;
 char *g_root;
+
 struct topo_hdl *g_thp;
+fmd_msg_hdl_t *g_msg;
 
 /*PRINTFLIKE2*/
 void
@@ -135,7 +137,7 @@
 static int
 usage(FILE *fp)
 {
-	(void) fprintf(fp, "Usage: %s [-efvV] [-c class] [-R root] [-t time] "
+	(void) fprintf(fp, "Usage: %s [-efmvV] [-c class] [-R root] [-t time] "
 	    "[-T time] [-u uuid]\n\t\t[-n name[.name]*[=value]] [file]\n",
 	    g_pname);
 
@@ -143,6 +145,7 @@
 	    "\t-c  select events that match the specified class\n"
 	    "\t-e  display error log content instead of fault log content\n"
 	    "\t-f  follow growth of log file by waiting for additional data\n"
+	    "\t-m  display human-readable messages for the fault log\n"
 	    "\t-R  set root directory for pathname expansions\n"
 	    "\t-t  select events that occurred after the specified time\n"
 	    "\t-T  select events that occurred before the specified time\n"
@@ -533,7 +536,7 @@
 int
 main(int argc, char *argv[])
 {
-	int opt_a = 0, opt_e = 0, opt_f = 0, opt_H = 0;
+	int opt_a = 0, opt_e = 0, opt_f = 0, opt_H = 0, opt_m = 0;
 	int opt_u = 0, opt_v = 0, opt_V = 0;
 
 	char ifile[PATH_MAX] = "";
@@ -565,7 +568,7 @@
 
 	while (optind < argc) {
 		while ((c =
-		    getopt(argc, argv, "ac:efHn:O:R:t:T:u:vV")) != EOF) {
+		    getopt(argc, argv, "ac:efHmn:O:R:t:T:u:vV")) != EOF) {
 			switch (c) {
 			case 'a':
 				opt_a++;
@@ -584,6 +587,9 @@
 			case 'H':
 				opt_H++;
 				break;
+			case 'm':
+				opt_m++;
+				break;
 			case 'O':
 				off = strtoull(optarg, NULL, 16);
 				iflags |= FMD_LOG_XITER_OFFS;
@@ -653,6 +659,12 @@
 		return (FMDUMP_EXIT_USAGE);
 	}
 
+	if ((g_msg = fmd_msg_init(g_root, FMD_MSG_VERSION)) == NULL) {
+		(void) fprintf(stderr, "%s: failed to initialize "
+		    "libfmd_msg: %s\n", g_pname, strerror(errno));
+		return (FMDUMP_EXIT_FATAL);
+	}
+
 	if ((lp = fmd_log_open(FMD_LOG_VERSION, ifile, &err)) == NULL) {
 		(void) fprintf(stderr, "%s: failed to open %s: %s\n",
 		    g_pname, ifile, fmd_log_errmsg(NULL, err));
@@ -700,9 +712,17 @@
 		iflags |= FMD_LOG_XITER_REFS;
 	} else if (opt_v) {
 		arg.da_fmt = &ops->do_formats[FMDUMP_VERB1];
+	} else if (opt_m) {
+		arg.da_fmt = &ops->do_formats[FMDUMP_MSG];
 	} else
 		arg.da_fmt = &ops->do_formats[FMDUMP_SHORT];
 
+	if (opt_m && arg.da_fmt->do_func == NULL) {
+		(void) fprintf(stderr, "%s: -m mode is not supported for "
+		    "log of type %s: %s\n", g_pname, fmd_log_label(lp), ifile);
+		return (FMDUMP_EXIT_USAGE);
+	}
+
 	arg.da_fv = errfv;
 	arg.da_fc = errfc;
 	arg.da_fp = stdout;
@@ -777,6 +797,11 @@
 	if (!opt_f && g_recs == 0 && isatty(STDOUT_FILENO))
 		(void) fprintf(stderr, "%s: %s is empty\n", g_pname, ifile);
 
+	if (g_thp != NULL)
+		topo_close(g_thp);
+
 	fmd_log_close(lp);
+	fmd_msg_fini(g_msg);
+
 	return (g_errs ? FMDUMP_EXIT_ERROR : FMDUMP_EXIT_SUCCESS);
 }
--- a/usr/src/cmd/fm/fmdump/common/fmdump.h	Wed Apr 29 03:44:12 2009 -0700
+++ b/usr/src/cmd/fm/fmdump/common/fmdump.h	Wed Apr 29 08:32:53 2009 -0700
@@ -2,9 +2,8 @@
  * CDDL HEADER START
  *
  * The contents of this file are subject to the terms of the
- * Common Development and Distribution License, Version 1.0 only
- * (the "License").  You may not use this file except in compliance
- * with the License.
+ * 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
  * or http://www.opensolaris.org/os/licensing.
@@ -20,15 +19,13 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
 #ifndef	_FMDUMP_H
 #define	_FMDUMP_H
 
-#pragma ident	"%Z%%M%	%I%	%E% SMI"
-
 #ifdef	__cplusplus
 extern "C" {
 #endif
@@ -38,13 +35,16 @@
 
 #include <sys/types.h>
 #include <sys/fm/protocol.h>
+
 #include <fm/fmd_log.h>
+#include <fm/fmd_msg.h>
 #include <fm/libtopo.h>
 
 enum {
 	FMDUMP_SHORT,
 	FMDUMP_VERB1,
 	FMDUMP_VERB2,
+	FMDUMP_MSG,
 	FMDUMP_NFMTS
 };
 
@@ -77,7 +77,9 @@
 extern ulong_t g_errs;
 extern ulong_t g_recs;
 extern char *g_root;
+
 extern struct topo_hdl *g_thp;
+extern fmd_msg_hdl_t *g_msg;
 
 extern void fmdump_printf(FILE *, const char *, ...);
 extern void fmdump_warn(const char *, ...);
--- a/usr/src/cmd/fm/fmtopo/common/fmtopo.c	Wed Apr 29 03:44:12 2009 -0700
+++ b/usr/src/cmd/fm/fmtopo/common/fmtopo.c	Wed Apr 29 08:32:53 2009 -0700
@@ -20,7 +20,7 @@
  */
 
 /*
- * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -49,7 +49,8 @@
 
 static const char *opt_R = "/";
 static const char *opt_s = FM_FMRI_SCHEME_HC;
-static const char optstr[] = "bCdeP:pR:s:StVx";
+static const char optstr[] = "bCdem:P:pR:s:StVx";
+static const char *opt_m;
 
 static int opt_b = 0;
 static int opt_d = 0;
@@ -76,13 +77,14 @@
 {
 	(void) fprintf(fp,
 	    "Usage: %s [-bCedpSVx] [-P group.property[=type:value]] "
-	    "[-R root] [-s scheme] [fmri]\n", g_pname);
+	    "[-R root] [-m method] [-s scheme] [fmri]\n", g_pname);
 
 	(void) fprintf(fp,
 	    "\t-b  walk in sibling-first order (default is child-first)\n"
 	    "\t-C  dump core after completing execution\n"
 	    "\t-d  set debug mode for libtopo modules\n"
 	    "\t-e  display FMRIs as paths using esc/eft notation\n"
+	    "\t-m  execute given method\n"
 	    "\t-P  get/set specified properties\n"
 	    "\t-p  display of FMRI protocol properties\n"
 	    "\t-R  set root directory for libtopo plug-ins and other files\n"
@@ -796,7 +798,7 @@
 {
 	int err;
 	nvlist_t *nvl;
-	nvlist_t *rsrc;
+	nvlist_t *rsrc, *out;
 	char *s;
 
 	if (opt_e && strcmp(opt_s, FM_FMRI_SCHEME_HC) == 0) {
@@ -818,15 +820,25 @@
 	}
 
 	if (g_fmri != NULL && fnmatch(g_fmri, s, 0) != 0) {
-			nvlist_free(rsrc);
-			topo_hdl_strfree(thp, s);
-			return (TOPO_WALK_NEXT);
+		nvlist_free(rsrc);
+		topo_hdl_strfree(thp, s);
+		return (TOPO_WALK_NEXT);
 	}
 
 	print_node(thp, node, rsrc, s);
 	topo_hdl_strfree(thp, s);
 	nvlist_free(rsrc);
 
+	if (opt_m != NULL) {
+		if (topo_method_invoke(node, opt_m, 0, NULL, &out, &err) == 0) {
+			nvlist_print(stdout, out);
+			nvlist_free(out);
+		} else if (err != ETOPO_METHOD_NOTSUP)
+			(void) fprintf(stderr, "%s: method failed unexpectedly "
+			    "on %s=%d (%s)\n", g_pname, topo_node_name(node),
+			    topo_node_instance(node), topo_strerror(err));
+	}
+
 	if (opt_V || opt_all) {
 		if ((nvl = topo_prop_getprops(node, &err)) == NULL) {
 			(void) fprintf(stderr, "%s: failed to get "
@@ -1175,6 +1187,9 @@
 			case 'e':
 				opt_e++;
 				break;
+			case 'm':
+				opt_m = optarg;
+				break;
 			case 'P':
 				pcnt++;
 				break;
--- a/usr/src/cmd/fm/modules/common/snmp-trapgen/Makefile	Wed Apr 29 03:44:12 2009 -0700
+++ b/usr/src/cmd/fm/modules/common/snmp-trapgen/Makefile	Wed Apr 29 08:32:53 2009 -0700
@@ -20,7 +20,7 @@
 #
 
 #
-# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
 #
@@ -35,7 +35,7 @@
 
 include ../../Makefile.plugin
 
-SNMPLIBS = -lnetsnmp -lnetsnmpagent
+SNMPLIBS = -lnetsnmp -lnetsnmpagent -lfmd_msg
 lint := SNMPLIBS =
 
 LDFLAGS += -L$(ROOT)/usr/lib/fm -R/usr/lib/fm
--- a/usr/src/cmd/fm/modules/common/snmp-trapgen/snmp.c	Wed Apr 29 03:44:12 2009 -0700
+++ b/usr/src/cmd/fm/modules/common/snmp-trapgen/snmp.c	Wed Apr 29 08:32:53 2009 -0700
@@ -20,12 +20,10 @@
  */
 
 /*
- * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
-#pragma ident	"%Z%%M%	%I%	%E% SMI"
-
 #include <sys/fm/protocol.h>
 #include <fm/fmd_api.h>
 #include <fm/fmd_snmp.h>
@@ -40,30 +38,6 @@
 #include <limits.h>
 #include <alloca.h>
 
-/*
- * SNMP_DOMAIN defines the dgettext() parameters the agent
- * can use to retrieve the localized format string for diagnosis messages.
- * The format string retrieved from SNMP_DOMAIN is the default format
- * string, but when processing each suspect list, dgettext() is also called
- * for the domain that matches the diagcode dictname.
- *
- * Similarly, SNMP_URL is also checked to see if snmp_url
- * should be overridden for each suspect list.
- *
- * The net effect of all this is that for a given diagcode DICT-1234-56:
- *
- *	- If DICT.mo defines snmp-url, it is used when filling
- *	  in the sunFmProblemURL variable.
- *
- *	- Otherwise, if snmp-trapgen.conf defines a "url" property, that
- *	  value is used.
- *
- *	- Otherwise, the default "http://sun.com/msg/" is used (via the
- *	  fmd_props[] table defined in this file).
- */
-static const char SNMP_DOMAIN[] = "FMD";
-static const char SNMP_URL[] = SNMP_URL_MSG;
-
 static struct stats {
 	fmd_stat_t bad_vers;
 	fmd_stat_t bad_code;
@@ -71,14 +45,13 @@
 	fmd_stat_t no_trap;
 } snmp_stats = {
 	{ "bad_vers", FMD_TYPE_UINT64, "event version is missing or invalid" },
-	{ "bad_code", FMD_TYPE_UINT64, "event code has no dictionary name" },
+	{ "bad_code", FMD_TYPE_UINT64, "failed to compute url for code" },
 	{ "bad_uuid", FMD_TYPE_UINT64, "event uuid is too long to send" },
 	{ "no_trap", FMD_TYPE_UINT64, "trap generation suppressed" }
 };
 
-static char *snmp_locdir;	/* l10n messages directory (if alternate) */
-static char *snmp_url;		/* current value of "url" property */
-static int snmp_trapall;	/* set to trap on all faults */
+static fmd_msg_hdl_t *snmp_msghdl;	/* handle for libfmd_msg */
+static int snmp_trapall;		/* set to trap on all faults */
 
 static const char SNMP_SUPPCONF[] = "fmd-trapgen";
 
@@ -167,13 +140,9 @@
 static void
 snmp_recv(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl, const char *class)
 {
-	char *uuid, *code, *dict, *url, *urlcode, *locdir, *p;
+	char *uuid, *code, *url;
 	boolean_t domsg;
-
 	uint8_t version;
-	char *olang = NULL;
-	int locale_c = 0;
-	size_t len;
 
 	if (nvlist_lookup_uint8(nvl, FM_VERSION, &version) != 0 ||
 	    version > FM_SUSPECT_VERSION) {
@@ -189,79 +158,17 @@
 		return;
 	}
 
-	/*
-	 * Extract the uuid and diagcode dictionary from the event code.  The
-	 * dictionary name is the text preceding the first "-" in the code.
-	 */
 	(void) nvlist_lookup_string(nvl, FM_SUSPECT_UUID, &uuid);
 	(void) nvlist_lookup_string(nvl, FM_SUSPECT_DIAG_CODE, &code);
 
-	if ((p = strchr(code, '-')) == NULL || p == code) {
-		fmd_hdl_debug(hdl, "invalid diagnosis code: %s\n", code);
-		snmp_stats.bad_code.fmds_value.ui64++;
-		return;
-	}
-
-	dict = alloca((size_t)(p - code) + 1);
-	(void) strncpy(dict, code, (size_t)(p - code));
-	dict[(size_t)(p - code)] = '\0';
-
-	fmd_msg_lock();
-
-	if (snmp_locdir != NULL)
-		locdir = bindtextdomain(dict, snmp_locdir);
-
-	if ((url = dgettext(dict, SNMP_URL)) == SNMP_URL) {
-		/*
-		 * We didn't find a translation in the dictionary for the
-		 * current language.  Fall back to C and try again.
-		 */
-		olang = setlocale(LC_MESSAGES, NULL);
-		if (olang) {
-			p = alloca(strlen(olang) + 1);
-			olang = strcpy(p, olang);
-		}
-		locale_c = 1;
-		(void) setlocale(LC_MESSAGES, "C");
-		if ((url = dgettext(dict, SNMP_URL)) == SNMP_URL)
-			url = snmp_url;
-	}
+	url = fmd_msg_getitem_nv(snmp_msghdl, NULL, nvl, FMD_MSG_ITEM_URL);
 
-	/*
-	 * If the URL ends with a slash, that indicates the code should be
-	 * appended to it.  After formatting the URL, reformat the DESC
-	 * text using the URL as an snprintf argument.
-	 */
-	len = strlen(url);
-	if (url[len - 1] == '/') {
-		urlcode = alloca(len + strlen(code) + 1);
-		(void) snprintf(urlcode, INT_MAX, "%s%s", url, code);
+	if (url != NULL) {
+		send_trap(hdl, uuid, code, url);
+		free(url);
 	} else {
-		urlcode = url;
-	}
-
-	/*
-	 * We have what we need; now send the trap.
-	 */
-	send_trap(hdl, uuid, code, urlcode);
-
-	/*
-	 * Switch back to our original language if we had to fall back to C.
-	 */
-	if (olang != NULL)
-		(void) setlocale(LC_MESSAGES, olang);
-
-	if (snmp_locdir != NULL)
-		(void) bindtextdomain(dict, locdir);
-
-	fmd_msg_unlock();
-
-	if (locale_c) {
-		fmd_hdl_debug(hdl,
-		    url == snmp_url ?
-		    "dgettext(%s, %s) in %s and C failed\n" :
-		    "dgettext(%s, %s) in %s failed; C used\n",
-		    dict, SNMP_URL, olang ? olang : "<null>");
+		fmd_hdl_debug(hdl, "failed to format url for %s", uuid);
+		snmp_stats.bad_code.fmds_value.ui64++;
 	}
 }
 
@@ -330,7 +237,7 @@
 void
 _fmd_init(fmd_hdl_t *hdl)
 {
-	char *rootdir, *locdir, *locale, *p;
+	char *rootdir, *urlbase;
 
 	if (fmd_hdl_register(hdl, FMD_API_VERSION, &fmd_info) != 0)
 		return; /* invalid data in configuration file */
@@ -341,72 +248,28 @@
 	if (init_sma() != SNMPERR_SUCCESS)
 		fmd_hdl_abort(hdl, "snmp-trapgen agent initialization failed");
 
-	/*
-	 * 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.
-	 */
-	if (getenv("NLSPATH") == NULL && putenv(fmd_hdl_strdup(hdl,
-	    "NLSPATH=/usr/lib/fm/fmd/fmd.cat", FMD_SLEEP)) != 0)
-		fmd_hdl_abort(hdl, "snmp-trapgen failed to set NLSPATH");
-
-	fmd_msg_lock();
-	(void) setlocale(LC_MESSAGES, "");
-	locale = setlocale(LC_MESSAGES, NULL);
-	if (locale) {
-		p = alloca(strlen(locale) + 1);
-		locale = strcpy(p, locale);
-	} else {
-		locale = "<null>";
-	}
-	fmd_msg_unlock();
-	fmd_hdl_debug(hdl, "locale=%s\n", locale);
+	rootdir = fmd_prop_get_string(hdl, "fmd.rootdir");
+	snmp_msghdl = fmd_msg_init(rootdir, FMD_MSG_VERSION);
+	fmd_prop_free_string(hdl, rootdir);
 
-	/*
-	 * Cache any properties we use every time we receive an event and
-	 * subscribe to list.suspect events regardless of the .conf file.
-	 */
-	snmp_url = fmd_prop_get_string(hdl, "url");
-	snmp_trapall = fmd_prop_get_int32(hdl, "trap_all");
-
-	/*
-	 * If fmd's rootdir property is set to a non-default root, then we are
-	 * going to need to rebind the text domains we use for dgettext() as
-	 * we go.  Look up the default l10n messages directory and make
-	 * snmp_locdir be this path with fmd.rootdir prepended to it.
-	 */
-	rootdir = fmd_prop_get_string(hdl, "fmd.rootdir");
+	if (snmp_msghdl == NULL)
+		fmd_hdl_abort(hdl, "failed to initialize libfmd_msg");
 
-	if (*rootdir != '\0' && strcmp(rootdir, "/") != 0) {
-		fmd_msg_lock();
-		locdir = bindtextdomain(SNMP_DOMAIN, NULL);
-		fmd_msg_unlock();
-		if (locdir != NULL) {
-			size_t len = strlen(rootdir) + strlen(locdir) + 1;
-			snmp_locdir = fmd_hdl_alloc(hdl, len, FMD_SLEEP);
-			(void) snprintf(snmp_locdir, len, "%s%s", rootdir,
-			    locdir);
-			fmd_hdl_debug(hdl,
-			    "binding textdomain to %s for snmp\n",
-			    snmp_locdir);
-		}
-	}
+	urlbase = fmd_prop_get_string(hdl, "url");
+	(void) fmd_msg_url_set(snmp_msghdl, urlbase);
+	fmd_prop_free_string(hdl, urlbase);
 
-	fmd_prop_free_string(hdl, rootdir);
+	snmp_trapall = fmd_prop_get_int32(hdl, "trap_all");
 	fmd_hdl_subscribe(hdl, FM_LIST_SUSPECT_CLASS);
 	fmd_hdl_subscribe(hdl, FM_LIST_REPAIRED_CLASS);
 	fmd_hdl_subscribe(hdl, FM_LIST_RESOLVED_CLASS);
 }
 
+/*ARGSUSED*/
 void
 _fmd_fini(fmd_hdl_t *hdl)
 {
-	fmd_hdl_strfree(hdl, snmp_locdir);
-	fmd_prop_free_string(hdl, snmp_url);
+	fmd_msg_fini(snmp_msghdl);
 
 	/*
 	 * snmp_shutdown, which we would normally use here, calls free_slots,
--- a/usr/src/cmd/fm/modules/common/syslog-msgs/syslog.c	Wed Apr 29 03:44:12 2009 -0700
+++ b/usr/src/cmd/fm/modules/common/syslog-msgs/syslog.c	Wed Apr 29 08:32:53 2009 -0700
@@ -18,6 +18,7 @@
  *
  * CDDL HEADER END
  */
+
 /*
  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
@@ -26,66 +27,28 @@
 #include <sys/fm/protocol.h>
 #include <sys/strlog.h>
 #include <sys/log.h>
+
 #include <fm/fmd_api.h>
 #include <fm/fmd_msg.h>
 
 #include <stropts.h>
+#include <strings.h>
 #include <syslog.h>
-#include <locale.h>
-#include <strings.h>
+#include <alloca.h>
+#include <unistd.h>
 #include <stdlib.h>
-#include <unistd.h>
-#include <limits.h>
-#include <alloca.h>
 #include <errno.h>
 #include <fcntl.h>
-#include <time.h>
-
-/*
- * SYSLOG_DOMAIN and SYSLOG_TEMPLATE define the dgettext() parameters the agent
- * can use to retrieve the localized format string for diagnosis messages.
- * The format string retrieved from SYSLOG_DOMAIN is the default format
- * string, but when processing each suspect list, dgettext() is also called
- * for the domain that matches the diagcode dictname and if SYSLOG_TEMPLATE
- * is defined, it overrides the default for that suspect list only.
- *
- * Similarly, SYSLOG_URL is also checked to see if syslog_url
- * should be overridden for each suspect list.
- *
- * The net effect of all this is that for a given diagcode DICT-1234-56:
- *
- *	- If DICT.mo defines syslog-msgs-message-template, it is used
- *	  as the format string for the diagnosis message.
- *
- *	- Otherwise, syslog-msgs-message-template from FMD.mo is used.
- *
- *	- If DICT.mo defines syslog-url, it is used when filling
- *	  in the %s in the "description" message.
- *
- *	- Otherwise, if syslog-msgs.conf defines a "url" property, that
- *	  value is used.
- *
- *	- Otherwise, the default "http://sun.com/msg/" is used (via the
- *	  fmd_props[] table defined in this file).
- */
-static const char SYSLOG_DOMAIN[] = "FMD";
-static const char SYSLOG_TEMPLATE[] = "syslog-msgs-message-template";
-static const char SYSLOG_URL[] = "syslog-url";
-static const char SYSLOG_POINTER[] = "syslog-msgs-pointer";
 
 static struct stats {
 	fmd_stat_t bad_vers;
-	fmd_stat_t bad_fmri;
 	fmd_stat_t bad_code;
-	fmd_stat_t bad_time;
 	fmd_stat_t log_err;
 	fmd_stat_t msg_err;
 	fmd_stat_t no_msg;
 } syslog_stats = {
 	{ "bad_vers", FMD_TYPE_UINT64, "event version is missing or invalid" },
-	{ "bad_fmri", FMD_TYPE_UINT64, "event fmri is missing or invalid" },
 	{ "bad_code", FMD_TYPE_UINT64, "event code has no dictionary name" },
-	{ "bad_time", FMD_TYPE_UINT64, "event time is not properly encoded" },
 	{ "log_err", FMD_TYPE_UINT64, "failed to log message to log(7D)" },
 	{ "msg_err", FMD_TYPE_UINT64, "failed to log message to sysmsg(7D)" },
 	{ "no_msg", FMD_TYPE_UINT64, "message logging suppressed" }
@@ -107,15 +70,14 @@
 	{ NULL, 0 }
 };
 
-static char *syslog_locdir;	/* l10n messages directory (if alternate) */
-static char *syslog_url;	/* current value of "url" property */
-static char *syslog_pointer;	/* info to point user to the full message */
+static fmd_msg_hdl_t *syslog_msghdl; /* handle for libfmd_msg calls */
 static int syslog_msgall;	/* set to message all faults */
 static log_ctl_t syslog_ctl;	/* log(7D) meta-data for each msg */
 static int syslog_logfd = -1;	/* log(7D) file descriptor */
 static int syslog_msgfd = -1;	/* sysmsg(7D) file descriptor */
 static int syslog_file;		/* log to syslog_logfd */
 static int syslog_cons;		/* log to syslog_msgfd */
+static const char SYSLOG_POINTER[] = "syslog-msgs-pointer";
 
 /*
  * Ideally we would just use syslog(3C) for outputting our messages, but our
@@ -139,77 +101,70 @@
  * set in the log_ctl_t.  The log driver allows us to set SL_LOGONLY when we
  * construct messages ourself, indicating that syslogd should only emit the
  * message to /var/adm/messages and any remote hosts, and skip the console.
- * Note: the log driver packet size limit for output via putmsg is LOGMAX_PS.
  * Then we emit the message a second time, without the special prefix, to the
  * sysmsg(7D) device, which handles console redirection and also permits us
  * to output any characters we like to the console, including \n and \r.
  */
-/*PRINTFLIKE2*/
 static void
-syslog_emit(fmd_hdl_t *hdl, const char *msgformat, ...)
+syslog_emit(fmd_hdl_t *hdl, const char *msg)
 {
 	struct strbuf ctl, dat;
 	uint32_t msgid;
 
-	char *format, *p, c;
-	char *buf = NULL;
-	size_t formatlen, logmsglen;
-	int len, plen;
-	va_list ap;
-
-	formatlen = strlen(msgformat) + 64; /* +64 for prefix and \0 */
-	format = alloca(formatlen);
-
-	STRLOG_MAKE_MSGID(msgformat, msgid);
-	(void) snprintf(format, formatlen,
-	    "fmd: [ID %u FACILITY_AND_PRIORITY] %s", msgid, msgformat);
+	char *buf;
+	size_t buflen;
 
-	/*
-	 * Figure out the length of the message then allocate a buffer
-	 * of adequate size.
-	 */
-	va_start(ap, msgformat);
-	if ((len = vsnprintf(&c, 1, format, ap)) >= 0 &&
-	    (buf = fmd_hdl_alloc(hdl, len + 1, FMD_SLEEP)) != NULL)
-		(void) vsnprintf(buf, len + 1, format, ap);
-	va_end(ap);
+	const char *format = "fmd: [ID %u FACILITY_AND_PRIORITY] %s";
+	STRLOG_MAKE_MSGID(format, msgid);
 
-	if (buf == NULL)
-		return;
+	buflen = snprintf(NULL, 0, format, msgid, msg);
+	buf = alloca(buflen + 1);
+	(void) snprintf(buf, buflen + 1, format, msgid, msg);
 
 	ctl.buf = (void *)&syslog_ctl;
 	ctl.len = sizeof (syslog_ctl);
 
 	dat.buf = buf;
-	logmsglen = strlen(buf) + 1;
+	dat.len = buflen + 1;
 
 	/*
-	 * The underlying log driver won't accept (ERANGE) messages
-	 * longer than LOG_MAXPS bytes.  The long message will be truncated
-	 * and appended with a pointer to the full message.
+	 * The underlying log driver won't accept messages longer than
+	 * LOG_MAXPS bytes.  Therefore, messages which exceed this limit will
+	 * be truncated and appended with a pointer to the full message.
 	 */
-	if (logmsglen <= LOG_MAXPS) {
-		dat.len = logmsglen;
-	} else {
-		plen = strlen(syslog_pointer) + 1;
-		buf[LOG_MAXPS - plen] = '\0';
-		/*
-		 * If possible, the pointer is appended after a newline
-		 */
-		if ((p = strrchr(buf, '\n')) == NULL)
-			p = &buf[LOG_MAXPS - plen];
+	if (dat.len > LOG_MAXPS) {
+		char *syslog_pointer, *p;
+		size_t plen;
 
-		(void) strcpy(p, syslog_pointer);
-		dat.len = strlen(buf) + 1;
+		if ((syslog_pointer = fmd_msg_gettext_id(syslog_msghdl, NULL,
+		    SYSLOG_POINTER)) == NULL) {
+			/*
+			 * This shouldn't happen, but if it does we'll just
+			 * truncate the message.
+			 */
+			buf[LOG_MAXPS - 1] = '\0';
+			dat.len = LOG_MAXPS;
+		} else {
+			plen = strlen(syslog_pointer) + 1;
+			buf[LOG_MAXPS - plen] = '\0';
+			/*
+			 * If possible, the pointer is appended after a newline
+			 */
+			if ((p = strrchr(buf, '\n')) == NULL)
+				p = &buf[LOG_MAXPS - plen];
+
+			(void) strcpy(p, syslog_pointer);
+			free(syslog_pointer);
+			dat.len = strlen(buf) + 1;
+		}
 	}
-
 	if (syslog_file && putmsg(syslog_logfd, &ctl, &dat, 0) != 0) {
 		fmd_hdl_debug(hdl, "putmsg failed: %s\n", strerror(errno));
 		syslog_stats.log_err.fmds_value.ui64++;
 	}
 
 	dat.buf = strchr(buf, ']');
-	dat.len = (size_t)(logmsglen - (dat.buf - buf));
+	dat.len -= (size_t)(dat.buf - buf);
 
 	dat.buf[0] = '\r'; /* overwrite ']' with carriage return */
 	dat.buf[1] = '\n'; /* overwrite ' ' with newline */
@@ -218,36 +173,15 @@
 		fmd_hdl_debug(hdl, "write failed: %s\n", strerror(errno));
 		syslog_stats.msg_err.fmds_value.ui64++;
 	}
-
-	fmd_hdl_free(hdl, buf, len + 1);
 }
 
 /*ARGSUSED*/
 static void
 syslog_recv(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl, const char *class)
 {
-	char *uuid, *code, *dict, *url, *urlcode, *template, *p;
-	char *src_name, *src_vers, *platform, *chassis, *server;
-	char *typ, *sev, *fmt, *trfmt, *rsp, *imp, *act, *locdir;
-	char desc[1024], date[64];
+	uint8_t version;
 	boolean_t domsg;
-
-	nvlist_t *fmri, *auth;
-	uint8_t version;
-	struct tm tm, *tmp;
-	int64_t *tv;
-	time_t sec;
-	uint_t tn = 0;
-	char *olang = NULL;
-	int locale_c = 0;
-	size_t len;
-
-	/*
-	 * don't log updated and isolated events (for now)
-	 */
-	if (strcmp(class, FM_LIST_ISOLATED_CLASS) == 0 ||
-	    strcmp(class, FM_LIST_UPDATED_CLASS) == 0)
-		return;
+	char *msg;
 
 	if (nvlist_lookup_uint8(nvl, FM_VERSION, &version) != 0 ||
 	    version > FM_SUSPECT_VERSION) {
@@ -263,166 +197,15 @@
 		return; /* event is not to be messaged */
 	}
 
-	/*
-	 * Extract the DE element, which is an FMRI for the diagnosis engine
-	 * that made this event, and validate its meta-data before continuing.
-	 */
-	if (nvlist_lookup_nvlist(nvl, FM_SUSPECT_DE, &fmri) != 0 ||
-	    nvlist_lookup_string(fmri, FM_FMRI_SCHEME, &p) != 0 ||
-	    strcmp(p, FM_FMRI_SCHEME_FMD) != 0 ||
-	    nvlist_lookup_uint8(fmri, FM_VERSION, &version) != 0 ||
-	    version > FM_FMD_SCHEME_VERSION ||
-	    nvlist_lookup_nvlist(fmri, FM_FMRI_AUTHORITY, &auth) != 0 ||
-	    nvlist_lookup_uint8(auth, FM_VERSION, &version) != 0 ||
-	    version > FM_FMRI_AUTH_VERSION) {
-		syslog_stats.bad_fmri.fmds_value.ui64++;
-		return; /* invalid de fmri */
-	}
 
-	/*
-	 * Extract the relevant identifying elements of the FMRI and authority.
-	 * Note: for now, we ignore FM_FMRI_AUTH_DOMAIN (only for SPs).
-	 */
-	(void) nvlist_lookup_string(fmri, FM_FMRI_FMD_NAME, &src_name);
-	(void) nvlist_lookup_string(fmri, FM_FMRI_FMD_VERSION, &src_vers);
-	(void) nvlist_lookup_string(auth, FM_FMRI_AUTH_PRODUCT, &platform);
-	(void) nvlist_lookup_string(auth, FM_FMRI_AUTH_SERVER, &server);
-
-	if (nvlist_lookup_string(auth, FM_FMRI_AUTH_CHASSIS, &chassis) != 0)
-		chassis = "-"; /* chassis serial number may not be present */
-
-	/*
-	 * Extract the uuid and diagcode dictionary from the event code.  The
-	 * dictionary name is the text preceding the first "-" in the code.
-	 */
-	(void) nvlist_lookup_string(nvl, FM_SUSPECT_UUID, &uuid);
-	(void) nvlist_lookup_string(nvl, FM_SUSPECT_DIAG_CODE, &code);
-
-	if ((p = strchr(code, '-')) == NULL || p == code) {
-		fmd_hdl_debug(hdl, "invalid diagnosis code: %s\n", code);
+	if ((msg = fmd_msg_gettext_nv(syslog_msghdl, NULL, nvl)) == NULL) {
+		fmd_hdl_debug(hdl, "failed to format message");
 		syslog_stats.bad_code.fmds_value.ui64++;
-		return; /* invalid diagnosis code */
+		return; /* libfmd_msg error */
 	}
 
-	dict = alloca((size_t)(p - code) + 1);
-	(void) strncpy(dict, code, (size_t)(p - code));
-	dict[(size_t)(p - code)] = '\0';
-
-	/*
-	 * Alloca a hunk of memory and use it to create the msgid strings
-	 * <code>.type, <code>.severity, <code>.description, and so forth.
-	 * These form the msgids we will use to look up the localized text.
-	 * Since we've allocated things to be of the right size, we know
-	 * than snprintf() can't overflow: INT_MAX is used shut lint up and
-	 * avoid code to needlessly recompute the remaining buffer space.
-	 */
-	typ = alloca(6 * (strlen(code) + 16));
-	sev = typ + snprintf(typ, INT_MAX, "%s.type", code) + 1;
-	fmt = sev + snprintf(sev, INT_MAX, "%s.severity", code) + 1;
-	rsp = fmt + snprintf(fmt, INT_MAX, "%s.description", code) + 1;
-	imp = rsp + snprintf(rsp, INT_MAX, "%s.response", code) + 1;
-	act = imp + snprintf(imp, INT_MAX, "%s.impact", code) + 1;
-	(void) snprintf(act, INT_MAX, "%s.action", code);
-
-	fmd_msg_lock();
-
-	if (syslog_locdir != NULL)
-		locdir =  bindtextdomain(dict, syslog_locdir);
-
-	if ((trfmt = dgettext(dict, fmt)) == fmt) {
-		/*
-		 * We didn't find a translation in the dictionary for the
-		 * current language.  The string we passed to gettext is merely
-		 * an index - it isn't sufficient, on its own, to be used as the
-		 * message.  Fall back to C and try again.
-		 */
-		olang = setlocale(LC_MESSAGES, NULL);
-		if (olang) {
-			p = alloca(strlen(olang) + 1);
-			olang = strcpy(p, olang);
-		}
-		locale_c = 1;
-		(void) setlocale(LC_MESSAGES, "C");
-		trfmt = dgettext(dict, fmt);
-	}
-
-	if ((url = dgettext(dict, SYSLOG_URL)) == SYSLOG_URL)
-		url = syslog_url;
-
-	/*
-	 * If the URL ends with a slash, that indicates the code should be
-	 * appended to it.  After formatting the URL, reformat the DESC
-	 * text using the URL as an snprintf argument.
-	 */
-	len = strlen(url);
-	if (url[len - 1] == '/') {
-		urlcode = alloca(len + strlen(code) + 1);
-		(void) snprintf(urlcode, INT_MAX, "%s%s", url, code);
-	} else {
-		urlcode = url;
-	}
-	/* LINTED - variable format specifier to snprintf() */
-	(void) snprintf(desc, sizeof (desc), trfmt, urlcode);
-
-	/*
-	 * Extract the diagnosis time and format it using the locale's default.
-	 * strftime() will use GMT or local time based on our "gmt" setting.
-	 */
-	if (nvlist_lookup_int64_array(nvl, FM_SUSPECT_DIAG_TIME,
-	    &tv, &tn) == 0 && tn == 2 && (sec = (time_t)tv[0]) != (time_t)-1 &&
-	    (tmp = localtime_r(&sec, &tm)) != NULL)
-		(void) strftime(date, sizeof (date), "%C", tmp);
-	else {
-		syslog_stats.bad_time.fmds_value.ui64++;
-		(void) strcpy(date, "-");
-	}
-
-	/*
-	 * Create and log the final string by filling in the template with the
-	 * strings we've created and the strings from the message dictionary.
-	 * If a template is provided for this dictionary, use it, otherwise
-	 * fall back to the default template.
-	 */
-	if ((template = dgettext(dict, SYSLOG_TEMPLATE)) == SYSLOG_TEMPLATE)
-		template = dgettext(SYSLOG_DOMAIN, SYSLOG_TEMPLATE);
-	/*
-	 * Do samely for the message pointer.  If the message is too long
-	 * to be handled by the underlying log drvier, the message will be
-	 * truncated and the pointer will be added to point user to the
-	 * full message.
-	 */
-	if ((syslog_pointer = dgettext(dict, SYSLOG_POINTER)) == SYSLOG_POINTER)
-		syslog_pointer = dgettext(SYSLOG_DOMAIN, SYSLOG_POINTER);
-
-	syslog_ctl.pri &= LOG_FACMASK;
-	if (strcmp(class, FM_LIST_RESOLVED_CLASS) == 0 ||
-	    strcmp(class, FM_LIST_REPAIRED_CLASS) == 0)
-		syslog_ctl.pri |= LOG_NOTICE;
-	else
-		syslog_ctl.pri |= LOG_ERR;
-	syslog_emit(hdl, template, code, dgettext(dict, typ),
-	    dgettext(dict, sev), date, platform, chassis, server, src_name,
-	    src_vers, uuid, desc, dgettext(dict, rsp), dgettext(dict, imp),
-	    dgettext(dict, act));
-
-	/*
-	 * Switch back to our original language if we had to fall back to C.
-	 */
-	if (olang != NULL)
-		(void) setlocale(LC_MESSAGES, olang);
-
-	if (syslog_locdir != NULL)
-		(void) bindtextdomain(dict, locdir);
-
-	fmd_msg_unlock();
-
-	if (locale_c) {
-		fmd_hdl_debug(hdl,
-		    trfmt == fmt ?
-		    "dgettext(%s, %s) in %s and C failed\n" :
-		    "dgettext(%s, %s) in %s failed; C used\n",
-		    dict, fmt, olang ? olang : "<null>");
-	}
+	syslog_emit(hdl, msg);
+	free(msg);
 }
 
 static const fmd_prop_t fmd_props[] = {
@@ -451,7 +234,7 @@
 _fmd_init(fmd_hdl_t *hdl)
 {
 	const struct facility *fp;
-	char *facname, *tz, *rootdir, *locdir, *locale, *p;
+	char *facname, *tz, *rootdir, *urlbase;
 
 	if (fmd_hdl_register(hdl, FMD_API_VERSION, &fmd_info) != 0)
 		return; /* invalid data in configuration file */
@@ -466,31 +249,6 @@
 		fmd_hdl_abort(hdl, "syslog-msgs failed to open /dev/sysmsg");
 
 	/*
-	 * 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.
-	 */
-	if (getenv("NLSPATH") == NULL && putenv(fmd_hdl_strdup(hdl,
-	    "NLSPATH=/usr/lib/fm/fmd/fmd.cat", FMD_SLEEP)) != 0)
-		fmd_hdl_abort(hdl, "syslog-msgs failed to set NLSPATH");
-
-	fmd_msg_lock();
-	(void) setlocale(LC_MESSAGES, "");
-	locale = setlocale(LC_MESSAGES, NULL);
-	if (locale) {
-		p = alloca(strlen(locale) + 1);
-		locale = strcpy(p, locale);
-	} else {
-		locale = "<null>";
-	}
-	fmd_msg_unlock();
-	fmd_hdl_debug(hdl, "locale=%s\n", locale);
-
-	/*
 	 * If the "gmt" property is set to true, force our EVENT-TIME to be
 	 * reported in GMT time; otherwise we use localtime.  tzset() affects
 	 * the results of subsequent calls to strftime(3C) above.
@@ -526,46 +284,29 @@
 	 */
 	syslog_file = fmd_prop_get_int32(hdl, "syslogd");
 	syslog_cons = fmd_prop_get_int32(hdl, "console");
-	syslog_url = fmd_prop_get_string(hdl, "url");
 	syslog_msgall = fmd_prop_get_int32(hdl, "message_all");
 
-	/*
-	 * If fmd's rootdir property is set to a non-default root, then we are
-	 * going to need to rebind the text domains we use for dgettext() as
-	 * we go.  Look up the default l10n messages directory and make
-	 * syslog_locdir be this path with fmd.rootdir prepended to it.
-	 */
 	rootdir = fmd_prop_get_string(hdl, "fmd.rootdir");
+	syslog_msghdl = fmd_msg_init(rootdir, FMD_MSG_VERSION);
+	fmd_prop_free_string(hdl, rootdir);
 
-	if (*rootdir != '\0' && strcmp(rootdir, "/") != 0) {
-		fmd_msg_lock();
-		locdir = bindtextdomain(SYSLOG_DOMAIN, NULL);
-		fmd_msg_unlock();
-		if (locdir != NULL) {
-			size_t len = strlen(rootdir) + strlen(locdir) + 1;
-			syslog_locdir = fmd_hdl_alloc(hdl, len, FMD_SLEEP);
-			(void) snprintf(syslog_locdir, len, "%s%s", rootdir,
-			    locdir);
-			fmd_hdl_debug(hdl,
-			    "binding textdomain to %s for syslog\n",
-			    syslog_locdir);
-		}
-	}
+	if (syslog_msghdl == NULL)
+		fmd_hdl_abort(hdl, "failed to initialize libfmd_msg");
 
-	fmd_prop_free_string(hdl, rootdir);
+	urlbase = fmd_prop_get_string(hdl, "url");
+	(void) fmd_msg_url_set(syslog_msghdl, urlbase);
+	fmd_prop_free_string(hdl, urlbase);
+
 	fmd_hdl_subscribe(hdl, FM_LIST_SUSPECT_CLASS);
-	fmd_hdl_subscribe(hdl, FM_LIST_UPDATED_CLASS);
-	fmd_hdl_subscribe(hdl, FM_LIST_ISOLATED_CLASS);
 	fmd_hdl_subscribe(hdl, FM_LIST_REPAIRED_CLASS);
 	fmd_hdl_subscribe(hdl, FM_LIST_RESOLVED_CLASS);
 }
 
+/*ARGSUSED*/
 void
 _fmd_fini(fmd_hdl_t *hdl)
 {
-	fmd_hdl_strfree(hdl, syslog_locdir);
-	fmd_prop_free_string(hdl, syslog_url);
-
+	fmd_msg_fini(syslog_msghdl);
 	(void) close(syslog_logfd);
 	(void) close(syslog_msgfd);
 }
--- a/usr/src/lib/fm/libfmd_msg/Makefile	Wed Apr 29 03:44:12 2009 -0700
+++ b/usr/src/lib/fm/libfmd_msg/Makefile	Wed Apr 29 08:32:53 2009 -0700
@@ -19,11 +19,9 @@
 # CDDL HEADER END
 #
 #
-# Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
-# ident	"%Z%%M%	%I%	%E% SMI"
-#
 
 include ../../Makefile.lib
 include ../Makefile.lib
@@ -39,10 +37,11 @@
 clobber := TARGET = clobber
 install := TARGET = install
 lint := TARGET = lint
+test := TARGET = test
 
 .KEEP_STATE:
 
-all clean clobber lint: $(SUBDIRS)
+all clean clobber lint test: $(SUBDIRS)
 
 install: install_h .WAIT $(SUBDIRS)
 
--- a/usr/src/lib/fm/libfmd_msg/Makefile.com	Wed Apr 29 03:44:12 2009 -0700
+++ b/usr/src/lib/fm/libfmd_msg/Makefile.com	Wed Apr 29 08:32:53 2009 -0700
@@ -19,7 +19,7 @@
 # CDDL HEADER END
 #
 #
-# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
 
@@ -40,11 +40,13 @@
 CPPFLAGS += -I../common -I.
 CFLAGS += $(CCVERBOSE) $(C_BIGPICFLAGS)
 CFLAGS64 += $(CCVERBOSE) $(C_BIGPICFLAGS)
-LDLIBS += -lc
+LDLIBS += -lnvpair -lc
 
 LINTFLAGS = -msux
 LINTFLAGS64 = -msux -m64
 
+CLOBBERFILES += fmd_msg_test fmd_msg_test.core fmd_msg_test.out
+
 $(LINTLIB) := SRCS = $(SRCDIR)/$(LINTSRC)
 $(LINTLIB) := LINTFLAGS = -nsvx
 $(LINTLIB) := LINTFLAGS64 = -nsvx -m64
@@ -57,3 +59,38 @@
 
 include ../../../Makefile.targ
 include ../../Makefile.targ
+
+LDLIBS_$(MACH) = -L$(ROOT)/usr/lib/fm -R$(ROOT)/usr/lib/fm
+LDLIBS_$(MACH64) = -L$(ROOT)/usr/lib/fm/$(MACH64) -R$(ROOT)/usr/lib/fm/$(MACH64)
+
+#
+# To ease the development and maintenance of libfmd_msg, a test suite is built
+# directly into the library.  The test program fmd_msg_test.c includes a set of
+# tests for all the code paths in the library.  The test program executes the
+# calls, and then forks into the background and dumps core.  After the test
+# runs, we diff the output against the master hand-verified output, and confirm
+# no leaks or corruption exist.  To run the entire suite, type "make test" and
+# inspect the results (the make target will fail on an error).
+#
+# The cmp skips the first 900 bytes of $(SRCDIR)/fmd_msg_test.out to get us
+# passed the CDDL header and copyright in that file.
+#
+test: install fmd_msg_test
+	@echo; echo "Running `pwd | sed 's/.*\///'` fmd_msg test suite ... \c"
+	@coreadm -p core $$$$
+	@UMEM_DEBUG=default,verbose UMEM_LOGGING=fail,contents LANG=C \
+	    LC_ALL=C ./fmd_msg_test | grep -v EVENT-TIME > fmd_msg_test.out
+	@chmod 0444 core; mv -f core fmd_msg_test.core
+	@echo; echo "Checking test output ... \c"
+	@cmp -s $(SRCDIR)/fmd_msg_test.out fmd_msg_test.out 900 0 && echo pass \
+	    || ( echo FAIL && diff $(SRCDIR)/fmd_msg_test.out fmd_msg_test.out )
+	@echo; echo Checking for memory leaks:
+	@echo ::findleaks | mdb fmd_msg_test.core
+	@echo; echo Checking for latent corruption:
+	@echo ::umem_verify | mdb fmd_msg_test.core | grep -v clean
+	@echo
+
+fmd_msg_test: $(SRCDIR)/fmd_msg_test.c
+	$(LINT.c) $(SRCDIR)/fmd_msg_test.c
+	$(LINK.c) -o fmd_msg_test $(SRCDIR)/fmd_msg_test.c \
+	    $(LDLIBS_$(TARGETMACH)) -lfmd_msg -lnvpair -lumem
--- a/usr/src/lib/fm/libfmd_msg/common/fmd_msg.c	Wed Apr 29 03:44:12 2009 -0700
+++ b/usr/src/lib/fm/libfmd_msg/common/fmd_msg.c	Wed Apr 29 08:32:53 2009 -0700
@@ -18,43 +18,1446 @@
  *
  * CDDL HEADER END
  */
+
 /*
- * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
-#pragma ident	"%Z%%M%	%I%	%E% SMI"
+/*
+ * 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_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://sun.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 <stdio.h>
+
+#include <sys/fm/protocol.h>
+
+#include <libintl.h>
+#include <locale.h>
+#include <wchar.h>
+
+#include <alloca.h>
+#include <assert.h>
+#include <pthread.h>
+#include <strings.h>
+#include <stdarg.h>
 #include <stdlib.h>
-#include <synch.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/sysmacros.h>
+
+#include <fmd_msg.h>
+
+#define	FMD_MSGBUF_SZ	256
+
+struct fmd_msg_hdl {
+	int fmh_version;	/* libfmd_msg client abi version number */
+	char *fmh_urlbase;	/* base url for all knowledge articles */
+	char *fmh_binding;	/* base directory for bindtextdomain() */
+	char *fmh_locale;	/* default program locale from environment */
+	const char *fmh_template; /* FMD_MSG_TEMPLATE value for fmh_locale */
+};
 
-static rwlock_t fmd_msg_rwlock;
+typedef struct fmd_msg_buf {
+	wchar_t *fmb_data;	/* wide-character data buffer */
+	size_t fmb_size;	/* size of fmb_data in wchar_t units */
+	size_t fmb_used;	/* used portion of fmb_data in wchar_t units */
+	int fmb_error;		/* error if any has occurred */
+} fmd_msg_buf_t;
+
+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 */
+};
 
-#pragma init(fmd_msg_init)
-static void
-fmd_msg_init(void)
+static pthread_rwlock_t fmd_msg_rwlock = PTHREAD_RWLOCK_INITIALIZER;
+
+static const char FMD_MSG_DOMAIN[] = "FMD";
+static const char FMD_MSG_TEMPLATE[] = "syslog-msgs-message-template";
+static const char FMD_MSG_URLKEY[] = "syslog-url";
+static const char FMD_MSG_URLBASE[] = "http://sun.com/msg/";
+static const char FMD_MSG_NLSPATH[] = "NLSPATH=/usr/lib/fm/fmd/fmd.cat";
+static const char FMD_MSG_MISSING[] = "-";
+
+/*
+ * 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 {
+	T_EOF,
+	T_ERR,
+	T_IDENT,
+	T_INT,
+	T_DOT,
+	T_LBRAC,
+	T_RBRAC
+} fmd_msg_nv_tkind_t;
+
+typedef struct fmd_msg_nv_token {
+	fmd_msg_nv_tkind_t t_kind;
+	union {
+		char tu_str[256];
+		uint_t tu_int;
+	} t_data;
+} fmd_msg_nv_token_t;
+
+static const struct fmd_msg_nv_type {
+	data_type_t nvt_type;
+	data_type_t nvt_base;
+	size_t nvt_size;
+	int (*nvt_value)();
+	int (*nvt_array)();
+} fmd_msg_nv_types[] = {
+	{ DATA_TYPE_INT8, DATA_TYPE_INT8,
+	    sizeof (int8_t), nvpair_value_int8, NULL },
+	{ DATA_TYPE_INT16, DATA_TYPE_INT16,
+	    sizeof (int16_t), nvpair_value_int16, NULL },
+	{ DATA_TYPE_INT32, DATA_TYPE_INT32,
+	    sizeof (int32_t), nvpair_value_int32, NULL },
+	{ DATA_TYPE_INT64, DATA_TYPE_INT64,
+	    sizeof (int64_t), nvpair_value_int64, NULL },
+	{ DATA_TYPE_UINT8, DATA_TYPE_UINT8,
+	    sizeof (uint8_t), nvpair_value_uint8, NULL },
+	{ DATA_TYPE_UINT16, DATA_TYPE_UINT16,
+	    sizeof (uint16_t), nvpair_value_uint16, NULL },
+	{ DATA_TYPE_UINT32, DATA_TYPE_UINT32,
+	    sizeof (uint32_t), nvpair_value_uint32, NULL },
+	{ DATA_TYPE_UINT64, DATA_TYPE_UINT64,
+	    sizeof (uint64_t), nvpair_value_uint64, NULL },
+	{ DATA_TYPE_BYTE, DATA_TYPE_BYTE,
+	    sizeof (uchar_t), nvpair_value_byte, NULL },
+	{ DATA_TYPE_BOOLEAN, DATA_TYPE_BOOLEAN,
+	    0, NULL, NULL },
+	{ DATA_TYPE_BOOLEAN_VALUE, DATA_TYPE_BOOLEAN_VALUE,
+	    sizeof (boolean_t), nvpair_value_boolean_value, NULL },
+	{ DATA_TYPE_HRTIME, DATA_TYPE_HRTIME,
+	    sizeof (hrtime_t), nvpair_value_hrtime, NULL },
+	{ DATA_TYPE_STRING, DATA_TYPE_STRING,
+	    sizeof (char *), nvpair_value_string, NULL },
+	{ DATA_TYPE_NVLIST, DATA_TYPE_NVLIST,
+	    sizeof (nvlist_t *), nvpair_value_nvlist, NULL },
+	{ DATA_TYPE_INT8_ARRAY, DATA_TYPE_INT8,
+	    sizeof (int8_t), NULL, nvpair_value_int8_array },
+	{ DATA_TYPE_INT16_ARRAY, DATA_TYPE_INT16,
+	    sizeof (int16_t), NULL, nvpair_value_int16_array },
+	{ DATA_TYPE_INT32_ARRAY, DATA_TYPE_INT32,
+	    sizeof (int32_t), NULL, nvpair_value_int32_array },
+	{ DATA_TYPE_INT64_ARRAY, DATA_TYPE_INT64,
+	    sizeof (int64_t), NULL, nvpair_value_int64_array },
+	{ DATA_TYPE_UINT8_ARRAY, DATA_TYPE_UINT8,
+	    sizeof (uint8_t), NULL, nvpair_value_uint8_array },
+	{ DATA_TYPE_UINT16_ARRAY, DATA_TYPE_UINT16,
+	    sizeof (uint16_t), NULL, nvpair_value_uint16_array },
+	{ DATA_TYPE_UINT32_ARRAY, DATA_TYPE_UINT32,
+	    sizeof (uint32_t), NULL, nvpair_value_uint32_array },
+	{ DATA_TYPE_UINT64_ARRAY, DATA_TYPE_UINT64,
+	    sizeof (uint64_t), NULL, nvpair_value_uint64_array },
+	{ DATA_TYPE_BYTE_ARRAY, DATA_TYPE_BYTE,
+	    sizeof (uchar_t), NULL, nvpair_value_byte_array },
+	{ DATA_TYPE_BOOLEAN_ARRAY, DATA_TYPE_BOOLEAN_VALUE,
+	    sizeof (boolean_t), NULL, nvpair_value_boolean_array },
+	{ DATA_TYPE_STRING_ARRAY, DATA_TYPE_STRING,
+	    sizeof (char *), NULL, nvpair_value_string_array },
+	{ DATA_TYPE_NVLIST_ARRAY, DATA_TYPE_NVLIST,
+	    sizeof (nvlist_t *), NULL, nvpair_value_nvlist_array },
+	{ DATA_TYPE_UNKNOWN, DATA_TYPE_UNKNOWN, 0, NULL, NULL }
+};
+
+static int fmd_msg_nv_parse_nvpair(fmd_msg_buf_t *, nvpair_t *, char *);
+static int fmd_msg_nv_parse_nvname(fmd_msg_buf_t *, nvlist_t *, char *);
+static int fmd_msg_nv_parse_nvlist(fmd_msg_buf_t *, nvlist_t *, char *);
+
+struct _rwlock;
+
+/*ARGSUSED*/
+static int
+fmd_msg_lock_held(fmd_msg_hdl_t *h)
 {
-	(void) rwlock_init(&fmd_msg_rwlock, USYNC_THREAD, NULL);
-}
-
-#pragma fini(fmd_msg_fini)
-static void
-fmd_msg_fini(void)
-{
-	(void) rwlock_destroy(&fmd_msg_rwlock);
+	extern int _rw_write_held(struct _rwlock *);
+	return (_rw_write_held((struct _rwlock *)&fmd_msg_rwlock));
 }
 
 void
 fmd_msg_lock(void)
 {
-	if (rw_wrlock(&fmd_msg_rwlock) != 0)
+	if (pthread_rwlock_wrlock(&fmd_msg_rwlock) != 0)
 		abort();
 }
 
 void
 fmd_msg_unlock(void)
 {
-	if (rw_unlock(&fmd_msg_rwlock) != 0)
+	if (pthread_rwlock_unlock(&fmd_msg_rwlock) != 0)
 		abort();
 }
+
+static fmd_msg_hdl_t *
+fmd_msg_init_err(fmd_msg_hdl_t *h, int err)
+{
+	fmd_msg_fini(h);
+	errno = err;
+	return (NULL);
+}
+
+fmd_msg_hdl_t *
+fmd_msg_init(const char *root, int version)
+{
+	fmd_msg_hdl_t *h = NULL;
+	const char *s;
+	size_t len;
+
+	if (version != FMD_MSG_VERSION)
+		return (fmd_msg_init_err(h, EINVAL));
+
+	if ((h = malloc(sizeof (fmd_msg_hdl_t))) == NULL)
+		return (fmd_msg_init_err(h, ENOMEM));
+
+	bzero(h, sizeof (fmd_msg_hdl_t));
+	h->fmh_version = version;
+
+	if ((h->fmh_urlbase = strdup(FMD_MSG_URLBASE)) == NULL)
+		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.
+	 */
+	(void) setlocale(LC_ALL, "");
+	s = setlocale(LC_ALL, NULL);
+	h->fmh_locale = strdup(s ? s : "C");
+
+	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
+	 * usr/lib/locale into <rootdir>/usr/lib/locale, but handles all of the
+	 * environ(5) settings that can change the default messages binding.
+	 */
+	if (root != NULL && root[0] != '\0' && strcmp(root, "/") != 0) {
+		if (root[0] != '/')
+			return (fmd_msg_init_err(h, EINVAL));
+
+		if ((s = bindtextdomain(FMD_MSG_DOMAIN, NULL)) == NULL)
+			s = "/usr/lib/locale"; /* substitute default */
+
+		len = strlen(root) + strlen(s) + 1;
+
+		if ((h->fmh_binding = malloc(len)) == NULL)
+			return (fmd_msg_init_err(h, ENOMEM));
+
+		(void) snprintf(h->fmh_binding, len, "%s%s", root, s);
+	}
+
+	/*
+	 * 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.
+	 */
+	if (getenv("NLSPATH") == NULL &&
+	    ((s = strdup(FMD_MSG_NLSPATH)) == NULL || putenv((char *)s) != 0))
+		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.
+	 */
+	h->fmh_template = dgettext(FMD_MSG_DOMAIN, FMD_MSG_TEMPLATE);
+
+	return (h);
+}
+
+void
+fmd_msg_fini(fmd_msg_hdl_t *h)
+{
+	if (h == NULL)
+		return; /* simplify caller code */
+
+	free(h->fmh_binding);
+	free(h->fmh_urlbase);
+	free(h->fmh_locale);
+	free(h);
+}
+
+int
+fmd_msg_locale_set(fmd_msg_hdl_t *h, const char *locale)
+{
+	char *l;
+
+	if (locale == NULL) {
+		errno = EINVAL;
+		return (-1);
+	}
+
+	if ((l = strdup(locale)) == NULL) {
+		errno = ENOMEM;
+		return (-1);
+	}
+
+	fmd_msg_lock();
+
+	if (setlocale(LC_ALL, l) == NULL) {
+		free(l);
+		errno = EINVAL;
+		fmd_msg_unlock();
+		return (-1);
+	}
+
+	h->fmh_template = dgettext(FMD_MSG_DOMAIN, FMD_MSG_TEMPLATE);
+	free(h->fmh_locale);
+	h->fmh_locale = l;
+
+	fmd_msg_unlock();
+	return (0);
+}
+
+const char *
+fmd_msg_locale_get(fmd_msg_hdl_t *h)
+{
+	return (h->fmh_locale);
+}
+
+int
+fmd_msg_url_set(fmd_msg_hdl_t *h, const char *url)
+{
+	char *u;
+
+	if (url == NULL) {
+		errno = EINVAL;
+		return (-1);
+	}
+
+	if ((u = strdup(url)) == NULL) {
+		errno = ENOMEM;
+		return (-1);
+	}
+
+	fmd_msg_lock();
+
+	free(h->fmh_urlbase);
+	h->fmh_urlbase = u;
+
+	fmd_msg_unlock();
+	return (0);
+}
+
+const char *
+fmd_msg_url_get(fmd_msg_hdl_t *h)
+{
+	return (h->fmh_urlbase);
+}
+
+static wchar_t *
+fmd_msg_mbstowcs(const char *s)
+{
+	size_t n = strlen(s) + 1;
+	wchar_t *w = malloc(n * sizeof (wchar_t));
+
+	if (w == NULL) {
+		errno = ENOMEM;
+		return (NULL);
+	}
+
+	if (mbstowcs(w, s, n) == (size_t)-1) {
+		free(w);
+		return (NULL);
+	}
+
+	return (w);
+}
+
+static void
+fmd_msg_buf_init(fmd_msg_buf_t *b)
+{
+	bzero(b, sizeof (fmd_msg_buf_t));
+	b->fmb_data = malloc(sizeof (wchar_t) * FMD_MSGBUF_SZ);
+
+	if (b->fmb_data == NULL)
+		b->fmb_error = ENOMEM;
+	else
+		b->fmb_size = FMD_MSGBUF_SZ;
+}
+
+static void
+fmd_msg_buf_fini(fmd_msg_buf_t *b)
+{
+	free(b->fmb_data);
+	bzero(b, sizeof (fmd_msg_buf_t));
+}
+
+static char *
+fmd_msg_buf_read(fmd_msg_buf_t *b)
+{
+	char *s;
+
+	if (b->fmb_error != 0) {
+		errno = b->fmb_error;
+		return (NULL);
+	}
+
+	if ((s = malloc(b->fmb_used * MB_CUR_MAX)) == NULL) {
+		errno = ENOMEM;
+		return (NULL);
+	}
+
+	if (wcstombs(s, b->fmb_data, b->fmb_used) == (size_t)-1) {
+		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
+fmd_msg_buf_write(fmd_msg_buf_t *b, const wchar_t *w, size_t n)
+{
+	if (b->fmb_used + n > b->fmb_size) {
+		size_t size = MAX(b->fmb_size * 2, b->fmb_used + n);
+		wchar_t *data = malloc(sizeof (wchar_t) * size);
+
+		if (data == NULL) {
+			if (b->fmb_error == 0)
+				b->fmb_error = ENOMEM;
+			return;
+		}
+
+		bcopy(b->fmb_data, data, b->fmb_used * sizeof (wchar_t));
+		free(b->fmb_data);
+
+		b->fmb_data = data;
+		b->fmb_size = size;
+	}
+
+	bcopy(w, &b->fmb_data[b->fmb_used], sizeof (wchar_t) * n);
+	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
+fmd_msg_buf_printf(fmd_msg_buf_t *b, const char *format, ...)
+{
+	ssize_t len;
+	va_list ap;
+	char *buf;
+	wchar_t *w;
+
+	va_start(ap, format);
+	len = vsnprintf(NULL, 0, format, ap);
+	buf = alloca(len + 1);
+	(void) vsnprintf(buf, len + 1, format, ap);
+	va_end(ap);
+
+	if ((w = fmd_msg_mbstowcs(buf)) == NULL) {
+		if (b->fmb_error != 0)
+			b->fmb_error = errno;
+	} else {
+		fmd_msg_buf_write(b, w, wcslen(w));
+		free(w);
+	}
+}
+
+/*PRINTFLIKE1*/
+static int
+fmd_msg_nv_error(const char *format, ...)
+{
+	int err = errno;
+	va_list ap;
+
+	if (getenv("FMD_MSG_DEBUG") == NULL)
+		return (1);
+
+	(void) fprintf(stderr, "libfmd_msg DEBUG: ");
+	va_start(ap, format);
+	(void) vfprintf(stderr, format, ap);
+	va_end(ap);
+
+	if (strchr(format, '\n') == NULL)
+		(void) fprintf(stderr, ": %s\n", strerror(err));
+
+	return (1);
+}
+
+static const struct fmd_msg_nv_type *
+fmd_msg_nv_type_lookup(data_type_t type)
+{
+	const struct fmd_msg_nv_type *t;
+
+	for (t = fmd_msg_nv_types; t->nvt_type != DATA_TYPE_UNKNOWN; t++) {
+		if (t->nvt_type == type)
+			break;
+	}
+
+	return (t);
+}
+
+/*
+ * Print the specified string, escaping any unprintable character sequences
+ * using the ISO C character escape sequences.
+ */
+static void
+fmd_msg_nv_print_string(fmd_msg_buf_t *b, const char *s)
+{
+	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:
+			fmd_msg_buf_printf(b, "\\x%02x", (uchar_t)c);
+		}
+	}
+}
+
+/*
+ * 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
+fmd_msg_nv_print_items(fmd_msg_buf_t *b, nvpair_t *nvp,
+    data_type_t type, void *p, uint_t n, uint_t idx)
+{
+	const struct fmd_msg_nv_type *nvt = fmd_msg_nv_type_lookup(type);
+	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",
+			    idx, nvpair_name(nvp), n ? n - 1 : 0));
+		}
+		p = (uchar_t *)p + nvt->nvt_size * idx;
+		n = 1;
+	}
+
+	for (i = 0; i < n; i++, p = (uchar_t *)p + nvt->nvt_size) {
+		if (i > 0)
+			fmd_msg_buf_printf(b, " "); /* array item delimiter */
+
+		switch (type) {
+		case DATA_TYPE_INT8:
+			fmd_msg_buf_printf(b, "%d", *(int8_t *)p);
+			break;
+
+		case DATA_TYPE_INT16:
+			fmd_msg_buf_printf(b, "%d", *(int16_t *)p);
+			break;
+
+		case DATA_TYPE_INT32:
+			fmd_msg_buf_printf(b, "%d", *(int32_t *)p);
+			break;
+
+		case DATA_TYPE_INT64:
+			fmd_msg_buf_printf(b, "%lld", *(longlong_t *)p);
+			break;
+
+		case DATA_TYPE_UINT8:
+			fmd_msg_buf_printf(b, "%u", *(uint8_t *)p);
+			break;
+
+		case DATA_TYPE_UINT16:
+			fmd_msg_buf_printf(b, "%u", *(uint16_t *)p);
+			break;
+
+		case DATA_TYPE_UINT32:
+			fmd_msg_buf_printf(b, "%u", *(uint32_t *)p);
+			break;
+
+		case DATA_TYPE_UINT64:
+			fmd_msg_buf_printf(b, "%llu", *(u_longlong_t *)p);
+			break;
+
+		case DATA_TYPE_BYTE:
+			fmd_msg_buf_printf(b, "0x%x", *(uchar_t *)p);
+			break;
+
+		case DATA_TYPE_BOOLEAN_VALUE:
+			fmd_msg_buf_printf(b,
+			    *(boolean_t *)p ? "true" : "false");
+			break;
+
+		case DATA_TYPE_HRTIME:
+			fmd_msg_buf_printf(b, "%lld", *(longlong_t *)p);
+			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
+fmd_msg_nv_print_nvpair(fmd_msg_buf_t *b, nvpair_t *nvp, uint_t idx)
+{
+	data_type_t type = nvpair_type(nvp);
+	const struct fmd_msg_nv_type *nvt = fmd_msg_nv_type_lookup(type);
+
+	uint64_t v;
+	void *a;
+	uint_t n;
+	int err;
+
+	if (nvt->nvt_type == DATA_TYPE_BOOLEAN) {
+		fmd_msg_buf_printf(b, "true");
+		err = 0;
+	} else if (nvt->nvt_array != NULL) {
+		(void) nvt->nvt_array(nvp, &a, &n);
+		err = fmd_msg_nv_print_items(b, nvp, nvt->nvt_base, a, n, idx);
+	} else if (nvt->nvt_value != NULL) {
+		(void) nvt->nvt_value(nvp, &v);
+		err = fmd_msg_nv_print_items(b, nvp, nvt->nvt_base, &v, 1, idx);
+	} else {
+		err = fmd_msg_nv_error("unknown data type %u", type);
+	}
+
+	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 *
+fmd_msg_nv_parse_token(char *s, fmd_msg_nv_token_t *tp)
+{
+	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;
+		tp->t_data.tu_int = strtoul(p, &q, 0);
+
+		if (errno != 0 || p == q) {
+			tp->t_kind = T_ERR;
+			return (p);
+		}
+
+		tp->t_kind = T_INT;
+		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;
+		}
+
+		if (sizeof (tp->t_data.tu_str) <= (size_t)(q - p)) {
+			tp->t_kind = T_ERR;
+			return (p);
+		}
+
+		bcopy(p, tp->t_data.tu_str, (size_t)(q - p));
+		tp->t_data.tu_str[(size_t)(q - p)] = '\0';
+		tp->t_kind = T_IDENT;
+		return (q);
+	}
+
+	switch (c) {
+	case '\0':
+		tp->t_kind = T_EOF;
+		return (p);
+	case '.':
+		tp->t_kind = T_DOT;
+		return (p + 1);
+	case '[':
+		tp->t_kind = T_LBRAC;
+		return (p + 1);
+	case ']':
+		tp->t_kind = T_RBRAC;
+		return (p + 1);
+	default:
+		tp->t_kind = T_ERR;
+		return (p);
+	}
+}
+
+static int
+fmd_msg_nv_parse_error(const char *s, fmd_msg_nv_token_t *tp)
+{
+	if (tp->t_kind == T_ERR)
+		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
+fmd_msg_nv_parse_array(fmd_msg_buf_t *b, nvpair_t *nvp, char *s1)
+{
+	fmd_msg_nv_token_t t;
+	nvlist_t **nva;
+	uint_t i, n;
+	char *s2;
+
+	if (fmd_msg_nv_type_lookup(nvpair_type(nvp))->nvt_array == NULL) {
+		return (fmd_msg_nv_error("inappropriate use of operator [ ]: "
+		    "element '%s' is not an array\n", nvpair_name(nvp)));
+	}
+
+	s2 = fmd_msg_nv_parse_token(s1, &t);
+	i = t.t_data.tu_int;
+
+	if (t.t_kind != T_INT)
+		return (fmd_msg_nv_error("expected integer index after [\n"));
+
+	s2 = fmd_msg_nv_parse_token(s2, &t);
+
+	if (t.t_kind != T_RBRAC)
+		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 (nvpair_type(nvp) == DATA_TYPE_NVLIST_ARRAY) {
+		(void) nvpair_value_nvlist_array(nvp, &nva, &n);
+
+		if (i >= n) {
+			return (fmd_msg_nv_error("index %u out-of-range for "
+			    "array %s: valid range is [0 .. %u]\n",
+			    i, nvpair_name(nvp), n ? n - 1 : 0));
+		}
+
+		return (fmd_msg_nv_parse_nvlist(b, nva[i], s2));
+	}
+
+	(void) fmd_msg_nv_parse_token(s2, &t);
+
+	if (t.t_kind != T_EOF) {
+		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
+fmd_msg_nv_parse_nvpair(fmd_msg_buf_t *b, nvpair_t *nvp, char *s1)
+{
+	fmd_msg_nv_token_t t;
+	nvlist_t *nvl;
+	char *s2;
+
+	s2 = fmd_msg_nv_parse_token(s1, &t);
+
+	if (t.t_kind == T_EOF)
+		return (fmd_msg_nv_print_nvpair(b, nvp, -1));
+
+	if (t.t_kind == T_LBRAC)
+		return (fmd_msg_nv_parse_array(b, nvp, s2));
+
+	if (t.t_kind != T_DOT)
+		return (fmd_msg_nv_parse_error(s1, &t));
+
+	if (nvpair_type(nvp) != DATA_TYPE_NVLIST) {
+		return (fmd_msg_nv_error("inappropriate use of operator '.': "
+		    "element '%s' is not of type nvlist\n", nvpair_name(nvp)));
+	}
+
+	(void) nvpair_value_nvlist(nvp, &nvl);
+	return (fmd_msg_nv_parse_nvname(b, nvl, s2));
+}
+
+/*
+ * Parse an expression for a name-value pair name (IDENT).  If we find a match
+ * continue parsing with the corresponding nvpair_t.
+ */
+static int
+fmd_msg_nv_parse_nvname(fmd_msg_buf_t *b, nvlist_t *nvl, char *s1)
+{
+	nvpair_t *nvp = NULL;
+	fmd_msg_nv_token_t t;
+	char *s2;
+
+	s2 = fmd_msg_nv_parse_token(s1, &t);
+
+	if (t.t_kind != T_IDENT)
+		return (fmd_msg_nv_parse_error(s1, &t));
+
+	while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
+		if (strcmp(nvpair_name(nvp), t.t_data.tu_str) == 0)
+			break;
+	}
+
+	if (nvp == NULL) {
+		return (fmd_msg_nv_error("no such name-value pair "
+		    "member: %s\n", t.t_data.tu_str));
+	}
+
+	return (fmd_msg_nv_parse_nvpair(b, nvp, s2));
+}
+
+/*
+ * 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
+fmd_msg_nv_parse_nvlist(fmd_msg_buf_t *b, nvlist_t *nvl, char *s1)
+{
+	fmd_msg_nv_token_t t;
+	char *s2;
+
+	s2 = fmd_msg_nv_parse_token(s1, &t);
+
+	if (t.t_kind == T_EOF)
+		return (0);
+
+	if (t.t_kind == T_DOT)
+		return (fmd_msg_nv_parse_nvname(b, nvl, s2));
+
+	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 *
+fmd_msg_getitem_locked(fmd_msg_hdl_t *h,
+    nvlist_t *nvl, const char *dict, const char *code, fmd_msg_item_t item)
+{
+	const char *istr = fmd_msg_items[item];
+	size_t len = strlen(code) + 1 + strlen(istr) + 1;
+	char *key = alloca(len);
+
+	fmd_msg_buf_t buf;
+	wchar_t *c, *u, *w, *p, *q;
+
+	const char *url, *txt;
+	char *s, *expr;
+	size_t elen;
+	int i;
+
+	assert(fmd_msg_lock_held(h));
+
+	/*
+	 * 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.
+	 */
+	if ((url = dgettext(dict, FMD_MSG_URLKEY)) == FMD_MSG_URLKEY)
+		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.  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) {
+		len = strlen(url) + strlen(code) + 1;
+		key = alloca(len);
+		(void) snprintf(key, len, "%s%s", url, code);
+		txt = key;
+	} else {
+		len = strlen(code) + 1 + strlen(istr) + 1;
+		key = alloca(len);
+		(void) snprintf(key, len, "%s.%s", code, istr);
+		txt = dgettext(dict, key);
+	}
+
+	c = fmd_msg_mbstowcs(code);
+	u = fmd_msg_mbstowcs(url);
+	w = fmd_msg_mbstowcs(txt);
+
+	if (c == NULL || u == NULL || w == NULL) {
+		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
+	 * %<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.
+	 */
+	fmd_msg_buf_init(&buf);
+
+	for (q = w, p = w; (p = wcschr(p, L'%')) != NULL; q = p) {
+		if (p > q)
+			fmd_msg_buf_write(&buf, q, (size_t)(p - q));
+
+		switch (p[1]) {
+		case L'%':
+			fmd_msg_buf_write(&buf, p, 1);
+			p += 2;
+			break;
+
+		case L's':
+			fmd_msg_buf_write(&buf, u, wcslen(u));
+			fmd_msg_buf_write(&buf, c, wcslen(c));
+
+			p += 2;
+			break;
+
+		case L'<':
+			q = p + 2;
+			p = wcschr(p + 2, L'>');
+
+			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.
+			 */
+			elen = (size_t)(p - q);
+			expr = malloc(elen + MB_CUR_MAX + 1);
+
+			if (expr == NULL) {
+				buf.fmb_error = ENOMEM;
+				goto eos;
+			}
+
+			for (i = 0; i < elen; i++)
+				(void) wctomb(&expr[i], q[i]);
+
+			expr[i] = '\0';
+
+			if (nvl != NULL)
+				(void) fmd_msg_nv_parse_nvname(&buf, nvl, expr);
+			else
+				fmd_msg_buf_printf(&buf, "%%<%s>", expr);
+
+			free(expr);
+			p++;
+			break;
+
+		case L'\0':
+			goto eos;
+
+		default:
+			p += 2;
+			break;
+		}
+	}
+eos:
+	fmd_msg_buf_write(&buf, q, wcslen(q) + 1);
+
+	free(c);
+	free(u);
+	free(w);
+
+	s = fmd_msg_buf_read(&buf);
+	fmd_msg_buf_fini(&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 *
+fmd_msg_gettext_locked(fmd_msg_hdl_t *h,
+    nvlist_t *nvl, const char *dict, const char *code)
+{
+	char *items[FMD_MSG_ITEM_MAX];
+	const char *format;
+	char *buf = NULL;
+	size_t len;
+	int i;
+
+	nvlist_t *fmri, *auth;
+	struct tm tm, *tmp;
+
+	int64_t *tv;
+	uint_t tn = 0;
+	time_t sec;
+	char date[64];
+
+	char *uuid, *src_name, *src_vers;
+	char *platform, *server, *chassis;
+
+	assert(fmd_msg_lock_held(h));
+	bzero(items, sizeof (items));
+
+	for (i = 0; i < FMD_MSG_ITEM_MAX; i++) {
+		items[i] = fmd_msg_getitem_locked(h, nvl, dict, code, i);
+		if (items[i] == NULL)
+			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.
+	 */
+	if ((format = dgettext(dict, FMD_MSG_TEMPLATE)) == FMD_MSG_TEMPLATE)
+		format = h->fmh_template;
+
+	if (nvlist_lookup_string(nvl, FM_SUSPECT_UUID, &uuid) != 0)
+		uuid = (char *)FMD_MSG_MISSING;
+
+	if (nvlist_lookup_int64_array(nvl, FM_SUSPECT_DIAG_TIME,
+	    &tv, &tn) == 0 && tn == 2 && (sec = (time_t)tv[0]) != (time_t)-1 &&
+	    (tmp = localtime_r(&sec, &tm)) != NULL)
+		(void) strftime(date, sizeof (date), "%C", tmp);
+	else
+		(void) strlcpy(date, FMD_MSG_MISSING, sizeof (date));
+
+	/*
+	 * Extract the relevant identifying elements of the FMRI and authority.
+	 * Note: for now, we ignore FM_FMRI_AUTH_DOMAIN (only for SPs).
+	 */
+	if (nvlist_lookup_nvlist(nvl, FM_SUSPECT_DE, &fmri) != 0)
+		fmri = NULL;
+
+	if (nvlist_lookup_nvlist(fmri, FM_FMRI_AUTHORITY, &auth) != 0)
+		auth = NULL;
+
+	if (nvlist_lookup_string(fmri, FM_FMRI_FMD_NAME, &src_name) != 0)
+		src_name = (char *)FMD_MSG_MISSING;
+
+	if (nvlist_lookup_string(fmri, FM_FMRI_FMD_VERSION, &src_vers) != 0)
+		src_vers = (char *)FMD_MSG_MISSING;
+
+	if (nvlist_lookup_string(auth, FM_FMRI_AUTH_PRODUCT, &platform) != 0)
+		platform = (char *)FMD_MSG_MISSING;
+
+	if (nvlist_lookup_string(auth, FM_FMRI_AUTH_SERVER, &server) != 0)
+		server = (char *)FMD_MSG_MISSING;
+
+	if (nvlist_lookup_string(auth, FM_FMRI_AUTH_CHASSIS, &chassis) != 0)
+		chassis = (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.
+	 */
+	len = snprintf(NULL, 0, format, code,
+	    items[FMD_MSG_ITEM_TYPE], items[FMD_MSG_ITEM_SEVERITY],
+	    date, platform, chassis, server, src_name, src_vers, uuid,
+	    items[FMD_MSG_ITEM_DESC], items[FMD_MSG_ITEM_RESPONSE],
+	    items[FMD_MSG_ITEM_IMPACT], items[FMD_MSG_ITEM_ACTION]);
+
+	if ((buf = malloc(len + 1)) == NULL) {
+		errno = ENOMEM;
+		goto out;
+	}
+
+	(void) snprintf(buf, len + 1, format, code,
+	    items[FMD_MSG_ITEM_TYPE], items[FMD_MSG_ITEM_SEVERITY],
+	    date, platform, chassis, server, src_name, src_vers, uuid,
+	    items[FMD_MSG_ITEM_DESC], items[FMD_MSG_ITEM_RESPONSE],
+	    items[FMD_MSG_ITEM_IMPACT], items[FMD_MSG_ITEM_ACTION]);
+out:
+	for (i = 0; i < FMD_MSG_ITEM_MAX; i++)
+		free(items[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 *
+fmd_msg_getitem(fmd_msg_hdl_t *h,
+    const char *locale, nvlist_t *nvl, const char *code, fmd_msg_item_t item)
+{
+	char *old_b, *old_c;
+	char *dict, *key, *p, *s;
+	size_t len;
+	int err;
+
+	if ((p = strchr(code, '-')) == NULL || p == code) {
+		errno = EINVAL;
+		return (NULL);
+	}
+
+	if (locale != NULL && strcmp(h->fmh_locale, locale) == 0)
+		locale = NULL; /* simplify later tests */
+
+	dict = alloca((size_t)(p - code) + 1);
+	(void) strncpy(dict, code, (size_t)(p - code));
+	dict[(size_t)(p - code)] = '\0';
+
+	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) {
+		p = bindtextdomain(dict, NULL);
+		old_b = alloca(strlen(p) + 1);
+		(void) strcpy(old_b, p);
+		(void) bindtextdomain(dict, h->fmh_binding);
+	}
+
+	/*
+	 * 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.
+	 */
+	len = strlen(code) + 1 + strlen(fmd_msg_items[FMD_MSG_ITEM_TYPE]) + 1;
+	key = alloca(len);
+
+	(void) snprintf(key, len, "%s.%s",
+	    code, fmd_msg_items[FMD_MSG_ITEM_TYPE]);
+
+	/*
+	 * 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.
+	 */
+	p = setlocale(LC_ALL, NULL);
+	old_c = alloca(strlen(p) + 1);
+	(void) strcpy(old_c, p);
+
+	if (locale != NULL)
+		(void) setlocale(LC_ALL, locale);
+
+	/*
+	 * 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 (dgettext(dict, key) == key &&
+	    (locale != NULL || strcmp(h->fmh_locale, "C") != 0)) {
+		(void) setlocale(LC_ALL, "C");
+		locale = "C"; /* restore locale */
+	}
+
+	if (dgettext(dict, key) == key) {
+		s = NULL;
+		err = ENOENT;
+	} else {
+		s = fmd_msg_getitem_locked(h, nvl, dict, code, item);
+		err = errno;
+	}
+
+	if (locale != NULL)
+		(void) setlocale(LC_ALL, old_c);
+
+	if (h->fmh_binding != NULL)
+		(void) bindtextdomain(dict, old_b);
+
+	fmd_msg_unlock();
+
+	if (s == NULL)
+		errno = err;
+
+	return (s);
+}
+
+char *
+fmd_msg_getitem_nv(fmd_msg_hdl_t *h,
+    const char *locale, nvlist_t *nvl, fmd_msg_item_t item)
+{
+	char *code;
+
+	if (item >= FMD_MSG_ITEM_MAX) {
+		errno = EINVAL;
+		return (NULL);
+	}
+
+	if (nvlist_lookup_string(nvl, FM_SUSPECT_DIAG_CODE, &code) != 0) {
+		errno = EINVAL;
+		return (NULL);
+	}
+
+	return (fmd_msg_getitem(h, locale, nvl, code, item));
+}
+
+char *
+fmd_msg_getitem_id(fmd_msg_hdl_t *h,
+    const char *locale, const char *code, fmd_msg_item_t item)
+{
+	if (item >= FMD_MSG_ITEM_MAX) {
+		errno = EINVAL;
+		return (NULL);
+	}
+
+	return (fmd_msg_getitem(h, locale, NULL, code, item));
+}
+
+/*
+ * 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 *
+fmd_msg_gettext(fmd_msg_hdl_t *h,
+    const char *locale, nvlist_t *nvl, const char *code)
+{
+	char *old_b, *old_c;
+	char *dict, *key, *p, *s;
+	size_t len;
+	int err;
+
+	if ((p = strchr(code, '-')) == NULL || p == code) {
+		errno = EINVAL;
+		return (NULL);
+	}
+
+	if (locale != NULL && strcmp(h->fmh_locale, locale) == 0)
+		locale = NULL; /* simplify later tests */
+
+	dict = alloca((size_t)(p - code) + 1);
+	(void) strncpy(dict, code, (size_t)(p - code));
+	dict[(size_t)(p - code)] = '\0';
+
+	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) {
+		p = bindtextdomain(dict, NULL);
+		old_b = alloca(strlen(p) + 1);
+		(void) strcpy(old_b, p);
+		(void) bindtextdomain(dict, h->fmh_binding);
+	}
+
+	/*
+	 * 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.
+	 */
+	len = strlen(code) + 1 + strlen(fmd_msg_items[FMD_MSG_ITEM_TYPE]) + 1;
+	key = alloca(len);
+
+	(void) snprintf(key, len, "%s.%s",
+	    code, fmd_msg_items[FMD_MSG_ITEM_TYPE]);
+
+	/*
+	 * 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.
+	 */
+	p = setlocale(LC_ALL, NULL);
+	old_c = alloca(strlen(p) + 1);
+	(void) strcpy(old_c, p);
+
+	if (locale != NULL)
+		(void) setlocale(LC_ALL, locale);
+
+	/*
+	 * 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 (dgettext(dict, key) == key &&
+	    (locale != NULL || strcmp(h->fmh_locale, "C") != 0)) {
+		(void) setlocale(LC_ALL, "C");
+		locale = "C"; /* restore locale */
+	}
+
+	if (dgettext(dict, key) == key) {
+		s = NULL;
+		err = ENOENT;
+	} else {
+		s = fmd_msg_gettext_locked(h, nvl, dict, code);
+		err = errno;
+	}
+
+	if (locale != NULL)
+		(void) setlocale(LC_ALL, old_c);
+
+	if (h->fmh_binding != NULL)
+		(void) bindtextdomain(dict, old_b);
+
+	fmd_msg_unlock();
+
+	if (s == NULL)
+		errno = err;
+
+	return (s);
+}
+
+char *
+fmd_msg_gettext_nv(fmd_msg_hdl_t *h, const char *locale, nvlist_t *nvl)
+{
+	char *code;
+
+	if (nvlist_lookup_string(nvl, FM_SUSPECT_DIAG_CODE, &code) != 0) {
+		errno = EINVAL;
+		return (NULL);
+	}
+
+	return (fmd_msg_gettext(h, locale, nvl, code));
+}
+
+char *
+fmd_msg_gettext_id(fmd_msg_hdl_t *h, const char *locale, const char *code)
+{
+	return (fmd_msg_gettext(h, locale, NULL, code));
+}
--- a/usr/src/lib/fm/libfmd_msg/common/fmd_msg.h	Wed Apr 29 03:44:12 2009 -0700
+++ b/usr/src/lib/fm/libfmd_msg/common/fmd_msg.h	Wed Apr 29 08:32:53 2009 -0700
@@ -18,15 +18,17 @@
  *
  * CDDL HEADER END
  */
+
 /*
- * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
 #ifndef	_FMD_MSG_H
 #define	_FMD_MSG_H
 
-#pragma ident	"%Z%%M%	%I%	%E% SMI"
+#include <sys/types.h>
+#include <sys/nvpair.h>
 
 #ifdef	__cplusplus
 extern "C" {
@@ -42,9 +44,42 @@
  * purpose until they are publicly documented for use outside of Sun.
  */
 
+#define	FMD_MSG_VERSION	1	/* libary ABI interface version */
+
+typedef struct fmd_msg_hdl fmd_msg_hdl_t;
+
+typedef enum {
+	FMD_MSG_ITEM_TYPE,
+	FMD_MSG_ITEM_SEVERITY,
+	FMD_MSG_ITEM_DESC,
+	FMD_MSG_ITEM_RESPONSE,
+	FMD_MSG_ITEM_IMPACT,
+	FMD_MSG_ITEM_ACTION,
+	FMD_MSG_ITEM_URL,
+	FMD_MSG_ITEM_MAX
+} fmd_msg_item_t;
+
 extern void fmd_msg_lock(void);
 extern void fmd_msg_unlock(void);
 
+fmd_msg_hdl_t *fmd_msg_init(const char *, int);
+void fmd_msg_fini(fmd_msg_hdl_t *);
+
+extern int fmd_msg_locale_set(fmd_msg_hdl_t *, const char *);
+extern const char *fmd_msg_locale_get(fmd_msg_hdl_t *);
+
+extern int fmd_msg_url_set(fmd_msg_hdl_t *, const char *);
+extern const char *fmd_msg_url_get(fmd_msg_hdl_t *);
+
+extern char *fmd_msg_gettext_nv(fmd_msg_hdl_t *, const char *, nvlist_t *);
+extern char *fmd_msg_gettext_id(fmd_msg_hdl_t *, const char *, const char *);
+
+extern char *fmd_msg_getitem_nv(fmd_msg_hdl_t *,
+    const char *, nvlist_t *, fmd_msg_item_t);
+
+extern char *fmd_msg_getitem_id(fmd_msg_hdl_t *,
+    const char *, const char *, fmd_msg_item_t);
+
 #ifdef	__cplusplus
 }
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/fm/libfmd_msg/common/fmd_msg_test.c	Wed Apr 29 08:32:53 2009 -0700
@@ -0,0 +1,279 @@
+/*
+ * 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
+ * or http://www.opensolaris.org/os/licensing.
+ * 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
+ */
+
+/*
+ * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <sys/fm/protocol.h>
+#include <fm/fmd_msg.h>
+
+#include <unistd.h>
+#include <signal.h>
+#include <strings.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define	TEST_ARR_SZ	2
+
+int
+main(int argc, char *argv[])
+{
+	fmd_msg_hdl_t *h;
+	pid_t pid;
+	int i, err = 0;
+	char *s;
+
+	nvlist_t *auth, *fmri, *list, *test_arr[TEST_ARR_SZ];
+	const char *code = "TEST-8000-08";
+	int64_t tod[] = { 0x9400000, 0 };
+
+	if (argc > 1) {
+		(void) fprintf(stderr, "Usage: %s\n", argv[0]);
+		return (2);
+	}
+
+	/*
+	 * Build up a valid list.suspect event for a fictional diagnosis
+	 * using a diagnosis code from our test dictionary so we can format
+	 * messages.
+	 */
+	if (nvlist_alloc(&auth, NV_UNIQUE_NAME, 0) != 0 ||
+	    nvlist_alloc(&fmri, NV_UNIQUE_NAME, 0) != 0 ||
+	    nvlist_alloc(&list, NV_UNIQUE_NAME, 0) != 0) {
+		(void) fprintf(stderr, "%s: nvlist_alloc failed\n", argv[0]);
+		return (1);
+	}
+
+	err |= nvlist_add_uint8(auth, FM_VERSION, FM_FMRI_AUTH_VERSION);
+	err |= nvlist_add_string(auth, FM_FMRI_AUTH_PRODUCT, "product");
+	err |= nvlist_add_string(auth, FM_FMRI_AUTH_CHASSIS, "chassis");
+	err |= nvlist_add_string(auth, FM_FMRI_AUTH_DOMAIN, "domain");
+	err |= nvlist_add_string(auth, FM_FMRI_AUTH_SERVER, "server");
+
+	if (err != 0) {
+		(void) fprintf(stderr, "%s: failed to build auth nvlist: %s\n",
+		    argv[0], strerror(err));
+		return (1);
+	}
+
+	err |= nvlist_add_uint8(fmri, FM_VERSION, FM_FMD_SCHEME_VERSION);
+	err |= nvlist_add_string(fmri, FM_FMRI_SCHEME, FM_FMRI_SCHEME_FMD);
+	err |= nvlist_add_nvlist(fmri, FM_FMRI_AUTHORITY, auth);
+	err |= nvlist_add_string(fmri, FM_FMRI_FMD_NAME, "fmd_msg_test");
+	err |= nvlist_add_string(fmri, FM_FMRI_FMD_VERSION, "1.0");
+
+	if (err != 0) {
+		(void) fprintf(stderr, "%s: failed to build fmri nvlist: %s\n",
+		    argv[0], strerror(err));
+		return (1);
+	}
+
+	err |= nvlist_add_uint8(list, FM_VERSION, FM_SUSPECT_VERSION);
+	err |= nvlist_add_string(list, FM_CLASS, FM_LIST_SUSPECT_CLASS);
+	err |= nvlist_add_string(list, FM_SUSPECT_UUID, "12345678");
+	err |= nvlist_add_string(list, FM_SUSPECT_DIAG_CODE, code);
+	err |= nvlist_add_int64_array(list, FM_SUSPECT_DIAG_TIME, tod, 2);
+	err |= nvlist_add_nvlist(list, FM_SUSPECT_DE, fmri);
+	err |= nvlist_add_uint32(list, FM_SUSPECT_FAULT_SZ, 0);
+
+	/*
+	 * Add a contrived nvlist array to our list.suspect so that we can
+	 * exercise the expansion syntax for dereferencing nvlist array members
+	 */
+	for (i = 0; i < TEST_ARR_SZ; i++) {
+		if (nvlist_alloc(&test_arr[i], NV_UNIQUE_NAME, 0) != 0) {
+			(void) fprintf(stderr, "%s: failed to alloc nvlist "
+			    "array: %s\n", argv[0], strerror(err));
+			return (1);
+		}
+		err |= nvlist_add_uint8(test_arr[i], "index", i);
+	}
+	err |= nvlist_add_nvlist_array(list, "test_arr", test_arr, TEST_ARR_SZ);
+
+	if (err != 0) {
+		(void) fprintf(stderr, "%s: failed to build list nvlist: %s\n",
+		    argv[0], strerror(err));
+		return (1);
+	}
+
+	/*
+	 * Now initialize the libfmd_msg library for testing, using the message
+	 * catalogs found in the proto area of the current workspace.
+	 */
+	if ((h = fmd_msg_init(getenv("ROOT"), FMD_MSG_VERSION)) == NULL) {
+		(void) fprintf(stderr, "%s: fmd_msg_init failed: %s\n",
+		    argv[0], strerror(errno));
+		return (1);
+	}
+
+	/*
+	 * Test 0: Verify that both fmd_msg_getitem_id and fmd_msg_gettext_id
+	 * return NULL and EINVAL for an illegal message code, and NULL
+	 * and ENOENT for a valid but not defined message code.
+	 */
+	s = fmd_msg_getitem_id(h, NULL, "I_AM_NOT_VALID", 0);
+	if (s != NULL || errno != EINVAL) {
+		(void) fprintf(stderr, "%s: test0 FAIL: illegal code returned "
+		    "s = %p, errno = %d\n", argv[0], (void *)s, errno);
+		return (1);
+	}
+
+	s = fmd_msg_gettext_id(h, NULL, "I_AM_NOT_VALID");
+	if (s != NULL || errno != EINVAL) {
+		(void) fprintf(stderr, "%s: test0 FAIL: illegal code returned "
+		    "s = %p, errno = %d\n", argv[0], (void *)s, errno);
+		return (1);
+	}
+
+	s = fmd_msg_getitem_id(h, NULL, "I_AM_NOT_HERE-0000-0000", 0);
+	if (s != NULL || errno != ENOENT) {
+		(void) fprintf(stderr, "%s: test0 FAIL: missing code returned "
+		    "s = %p, errno = %d\n", argv[0], (void *)s, errno);
+		return (1);
+	}
+
+	s = fmd_msg_gettext_id(h, NULL, "I_AM_NOT_HERE-0000-0000");
+	if (s != NULL || errno != ENOENT) {
+		(void) fprintf(stderr, "%s: test0 FAIL: missing code returned "
+		    "s = %p, errno = %d\n", argv[0], (void *)s, errno);
+		return (1);
+	}
+
+	/*
+	 * Test 1: Use fmd_msg_getitem_id to retrieve the item strings for
+	 * a known message code without having any actual event handle.
+	 */
+	for (i = 0; i < FMD_MSG_ITEM_MAX; i++) {
+		if ((s = fmd_msg_getitem_id(h, NULL, code, i)) == NULL) {
+			(void) fprintf(stderr, "%s: fmd_msg_getitem_id failed "
+			    "for %s, item %d: %s\n",
+			    argv[0], code, i, strerror(errno));
+		}
+
+		(void) printf("code %s item %d = <<%s>>\n", code, i, s);
+		free(s);
+	}
+
+	/*
+	 * Test 2: Use fmd_msg_gettext_id to retrieve the complete message for
+	 * a known message code without having any actual event handle.
+	 */
+	if ((s = fmd_msg_gettext_id(h, NULL, code)) == NULL) {
+		(void) fprintf(stderr, "%s: fmd_msg_gettext_id failed for %s: "
+		    "%s\n", argv[0], code, strerror(errno));
+		return (1);
+	}
+
+	(void) printf("%s\n", s);
+	free(s);
+
+	/*
+	 * Test 3: Use fmd_msg_getitem_nv to retrieve the item strings for
+	 * our list.suspect event handle.
+	 */
+	for (i = 0; i < FMD_MSG_ITEM_MAX; i++) {
+		if ((s = fmd_msg_getitem_nv(h, NULL, list, i)) == NULL) {
+			(void) fprintf(stderr, "%s: fmd_msg_getitem_nv failed "
+			    "for %s, item %d: %s\n",
+			    argv[0], code, i, strerror(errno));
+		}
+
+		(void) printf("code %s item %d = <<%s>>\n", code, i, s);
+		free(s);
+	}
+
+	/*
+	 * Test 4: Use fmd_msg_getitem_nv to retrieve the complete message for
+	 * a known message code using our list.suspect event handle.
+	 */
+	if ((s = fmd_msg_gettext_nv(h, NULL, list)) == NULL) {
+		(void) fprintf(stderr, "%s: fmd_msg_gettext_nv failed for %s: "
+		    "%s\n", argv[0], code, strerror(errno));
+		return (1);
+	}
+
+	(void) printf("%s\n", s);
+	free(s);
+
+	/*
+	 * Test 5: Use fmd_msg_getitem_nv to retrieve the complete message for
+	 * a known message code using our list.suspect event handle, but this
+	 * time set the URL to our own customized URL.  Our contrived message
+	 * has been designed to exercise the key aspects of the variable
+	 * expansion syntax.
+	 */
+	if (fmd_msg_url_set(h, "http://foo.bar.com/") != 0) {
+		(void) fprintf(stderr, "%s: fmd_msg_url_set failed: %s\n",
+		    argv[0], strerror(errno));
+	}
+
+	if ((s = fmd_msg_gettext_nv(h, NULL, list)) == NULL) {
+		(void) fprintf(stderr, "%s: fmd_msg_gettext_nv failed for %s: "
+		    "%s\n", argv[0], code, strerror(errno));
+		return (1);
+	}
+
+	(void) printf("%s\n", s);
+	free(s);
+
+	for (i = 0; i < TEST_ARR_SZ; i++)
+		nvlist_free(test_arr[i]);
+	nvlist_free(fmri);
+	nvlist_free(auth);
+	nvlist_free(list);
+
+	fmd_msg_fini(h);	/* free library state before dumping core */
+	pid = fork();		/* fork into background to not bother make(1) */
+
+	switch (pid) {
+	case -1:
+		(void) fprintf(stderr, "FAIL (failed to fork)\n");
+		return (1);
+	case 0:
+		abort();
+		return (1);
+	}
+
+	if (waitpid(pid, &err, 0) == -1) {
+		(void) fprintf(stderr, "FAIL (failed to wait for %d: %s)\n",
+		    (int)pid, strerror(errno));
+		return (1);
+	}
+
+	if (WIFSIGNALED(err) == 0 || WTERMSIG(err) != SIGABRT) {
+		(void) fprintf(stderr, "FAIL (child did not SIGABRT)\n");
+		return (1);
+	}
+
+	if (!WCOREDUMP(err)) {
+		(void) fprintf(stderr, "FAIL (no core generated)\n");
+		return (1);
+	}
+
+	(void) fprintf(stderr, "done\n");
+	return (0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/fm/libfmd_msg/common/fmd_msg_test.out	Wed Apr 29 08:32:53 2009 -0700
@@ -0,0 +1,106 @@
+#
+# 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
+# or http://www.opensolaris.org/os/licensing.
+# 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
+#
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+code TEST-8000-08 item 0 = <<Defect>>
+code TEST-8000-08 item 1 = <<Minor>>
+code TEST-8000-08 item 2 = <<This entry tests URL variable expansion - url = http://sun.com/msg/TEST-8000-08>>
+code TEST-8000-08 item 3 = <<This entry tests the percent character escape sequence: %>>
+code TEST-8000-08 item 4 = <<This entry tests variable expansion for event payload members: uuid = %<uuid>, de scheme = %<de.scheme>>>
+code TEST-8000-08 item 5 = <<Variable expansion for arrays: index = %<test_arr[1].index>>>
+code TEST-8000-08 item 6 = <<http://sun.com/msg/TEST-8000-08>>
+SUNW-MSG-ID: TEST-8000-08, TYPE: Defect, VER: 1, SEVERITY: Minor
+PLATFORM: -, CSN: -, HOSTNAME: -
+SOURCE: -, REV: -
+EVENT-ID: -
+DESC: This entry tests URL variable expansion - url = http://sun.com/msg/TEST-8000-08
+AUTO-RESPONSE: This entry tests the percent character escape sequence: %
+IMPACT: This entry tests variable expansion for event payload members: uuid = %<uuid>, de scheme = %<de.scheme>
+REC-ACTION: Variable expansion for arrays: index = %<test_arr[1].index>
+
+code TEST-8000-08 item 0 = <<Defect>>
+code TEST-8000-08 item 1 = <<Minor>>
+code TEST-8000-08 item 2 = <<This entry tests URL variable expansion - url = http://sun.com/msg/TEST-8000-08>>
+code TEST-8000-08 item 3 = <<This entry tests the percent character escape sequence: %>>
+code TEST-8000-08 item 4 = <<This entry tests variable expansion for event payload members: uuid = 12345678, de scheme = fmd>>
+code TEST-8000-08 item 5 = <<Variable expansion for arrays: index = 1>>
+code TEST-8000-08 item 6 = <<http://sun.com/msg/TEST-8000-08>>
+SUNW-MSG-ID: TEST-8000-08, TYPE: Defect, VER: 1, SEVERITY: Minor
+PLATFORM: product, CSN: chassis, HOSTNAME: server
+SOURCE: fmd_msg_test, REV: 1.0
+EVENT-ID: 12345678
+DESC: This entry tests URL variable expansion - url = http://sun.com/msg/TEST-8000-08
+AUTO-RESPONSE: This entry tests the percent character escape sequence: %
+IMPACT: This entry tests variable expansion for event payload members: uuid = 12345678, de scheme = fmd
+REC-ACTION: Variable expansion for arrays: index = 1
+
+SUNW-MSG-ID: TEST-8000-08, TYPE: Defect, VER: 1, SEVERITY: Minor
+PLATFORM: product, CSN: chassis, HOSTNAME: server
+SOURCE: fmd_msg_test, REV: 1.0
+EVENT-ID: 12345678
+DESC: This entry tests URL variable expansion - url = http://foo.bar.com/TEST-8000-08
+AUTO-RESPONSE: This entry tests the percent character escape sequence: %
+IMPACT: This entry tests variable expansion for event payload members: uuid = 12345678, de scheme = fmd
+REC-ACTION: Variable expansion for arrays: index = 1
+
+code TEST-8000-08 item 0 = <<Defect>>
+code TEST-8000-08 item 1 = <<Minor>>
+code TEST-8000-08 item 2 = <<This entry tests URL variable expansion - url = http://sun.com/msg/TEST-8000-08>>
+code TEST-8000-08 item 3 = <<This entry tests the percent character escape sequence: %>>
+code TEST-8000-08 item 4 = <<This entry tests variable expansion for event payload members: uuid = %<uuid>, de scheme = %<de.scheme>>>
+code TEST-8000-08 item 5 = <<Variable expansion for arrays: index = %<test_arr[1].index>>>
+code TEST-8000-08 item 6 = <<http://sun.com/msg/TEST-8000-08>>
+SUNW-MSG-ID: TEST-8000-08, TYPE: Defect, VER: 1, SEVERITY: Minor
+PLATFORM: -, CSN: -, HOSTNAME: -
+SOURCE: -, REV: -
+EVENT-ID: -
+DESC: This entry tests URL variable expansion - url = http://sun.com/msg/TEST-8000-08
+AUTO-RESPONSE: This entry tests the percent character escape sequence: %
+IMPACT: This entry tests variable expansion for event payload members: uuid = %<uuid>, de scheme = %<de.scheme>
+REC-ACTION: Variable expansion for arrays: index = %<test_arr[1].index>
+
+code TEST-8000-08 item 0 = <<Defect>>
+code TEST-8000-08 item 1 = <<Minor>>
+code TEST-8000-08 item 2 = <<This entry tests URL variable expansion - url = http://sun.com/msg/TEST-8000-08>>
+code TEST-8000-08 item 3 = <<This entry tests the percent character escape sequence: %>>
+code TEST-8000-08 item 4 = <<This entry tests variable expansion for event payload members: uuid = 12345678, de scheme = fmd>>
+code TEST-8000-08 item 5 = <<Variable expansion for arrays: index = 1>>
+code TEST-8000-08 item 6 = <<http://sun.com/msg/TEST-8000-08>>
+SUNW-MSG-ID: TEST-8000-08, TYPE: Defect, VER: 1, SEVERITY: Minor
+PLATFORM: product, CSN: chassis, HOSTNAME: server
+SOURCE: fmd_msg_test, REV: 1.0
+EVENT-ID: 12345678
+DESC: This entry tests URL variable expansion - url = http://sun.com/msg/TEST-8000-08
+AUTO-RESPONSE: This entry tests the percent character escape sequence: %
+IMPACT: This entry tests variable expansion for event payload members: uuid = 12345678, de scheme = fmd
+REC-ACTION: Variable expansion for arrays: index = 1
+
+SUNW-MSG-ID: TEST-8000-08, TYPE: Defect, VER: 1, SEVERITY: Minor
+PLATFORM: product, CSN: chassis, HOSTNAME: server
+SOURCE: fmd_msg_test, REV: 1.0
+EVENT-ID: 12345678
+DESC: This entry tests URL variable expansion - url = http://foo.bar.com/TEST-8000-08
+AUTO-RESPONSE: This entry tests the percent character escape sequence: %
+IMPACT: This entry tests variable expansion for event payload members: uuid = 12345678, de scheme = fmd
+REC-ACTION: Variable expansion for arrays: index = 1
+
--- a/usr/src/lib/fm/libfmd_msg/common/mapfile-vers	Wed Apr 29 03:44:12 2009 -0700
+++ b/usr/src/lib/fm/libfmd_msg/common/mapfile-vers	Wed Apr 29 08:32:53 2009 -0700
@@ -22,8 +22,6 @@
 # Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
-
-#
 # MAPFILE HEADER START
 #
 # WARNING:  STOP NOW.  DO NOT MODIFY THIS FILE.
@@ -39,8 +37,18 @@
 
 SUNWprivate {
     global:
+	fmd_msg_fini;
+	fmd_msg_getitem_id;
+	fmd_msg_getitem_nv;
+	fmd_msg_gettext_id;
+	fmd_msg_gettext_nv;
+	fmd_msg_init;
+	fmd_msg_locale_get;
+	fmd_msg_locale_set;
 	fmd_msg_lock;
 	fmd_msg_unlock;
+	fmd_msg_url_get;
+	fmd_msg_url_set;
     local:
 	*;
 };
--- a/usr/src/pkgdefs/etc/exception_list_i386	Wed Apr 29 03:44:12 2009 -0700
+++ b/usr/src/pkgdefs/etc/exception_list_i386	Wed Apr 29 08:32:53 2009 -0700
@@ -1239,3 +1239,10 @@
 
 # sbd ioctl hdr
 usr/include/sys/stmf_sbd_ioctl.h	i386
+
+#
+# portable object file and dictionary used by libfmd_msg test
+#
+usr/lib/fm/dict/TEST.dict		i386
+usr/lib/locale/C/LC_MESSAGES/TEST.mo	i386
+usr/lib/locale/C/LC_MESSAGES/TEST.po	i386
--- a/usr/src/pkgdefs/etc/exception_list_sparc	Wed Apr 29 03:44:12 2009 -0700
+++ b/usr/src/pkgdefs/etc/exception_list_sparc	Wed Apr 29 08:32:53 2009 -0700
@@ -1338,3 +1338,10 @@
 
 # sbd ioctl hdr
 usr/include/sys/stmf_sbd_ioctl.h	sparc
+
+#
+# portable object file and dictionary used by libfmd_msg test
+#
+usr/lib/fm/dict/TEST.dict		sparc
+usr/lib/locale/C/LC_MESSAGES/TEST.mo	sparc
+usr/lib/locale/C/LC_MESSAGES/TEST.po	sparc