view usr/src/lib/fm/libfmnotify/common/libfmnotify.c @ 12979:ab9ae749152f

PSARC/2009/617 Software Events Notification Parameters CLI PSARC/2009/618 snmp-notify: SNMP Notification Daemon for Software Events PSARC/2009/619 smtp-notify: Email Notification Daemon for Software Events PSARC/2010/225 fmd for non-global Solaris zones PSARC/2010/226 Solaris Instance UUID PSARC/2010/227 nvlist_nvflag(3NVPAIR) PSARC/2010/228 libfmevent additions PSARC/2010/257 sysevent_evc_setpropnvl and sysevent_evc_getpropnvl PSARC/2010/265 FMRI and FMA Event Stabilty, 'ireport' category 1 event class, and the 'sw' FMRI scheme PSARC/2010/278 FMA/SMF integration: instance state transitions PSARC/2010/279 Modelling panics within FMA PSARC/2010/290 logadm.conf upgrade 6392476 fmdump needs to pretty-print 6393375 userland ereport/ireport event generation interfaces 6445732 Add email notification agent for FMA and software events 6804168 RFE: Allow an efficient means to monitor SMF services status changes 6866661 scf_values_destroy(3SCF) will segfault if is passed NULL 6884709 Add snmp notification agent for FMA and software events 6884712 Add private interface to tap into libfmd_msg macro expansion capabilities 6897919 fmd to run in a non-global zone 6897937 fmd use of non-private doors is not safe 6900081 add a UUID to Solaris kernel image for use in crashdump identification 6914884 model panic events as a defect diagnosis in FMA 6944862 fmd_case_open_uuid, fmd_case_uuisresolved, fmd_nvl_create_defect 6944866 log legacy sysevents in fmd 6944867 enumerate svc scheme in topo 6944868 software-diagnosis and software-response fmd modules 6944870 model SMF maintenance state as a defect diagnosis in FMA 6944876 savecore runs in foreground for systems with zfs root and dedicated dump 6965796 Implement notification parameters for SMF state transitions and FMA events 6968287 SUN-FM-MIB.mib needs to be updated to reflect Oracle information 6972331 logadm.conf upgrade PSARC/2010/290
author Gavin Maltby <gavin.maltby@oracle.com>
date Fri, 30 Jul 2010 17:04:17 +1000
parents
children
line wrap: on
line source

/*
 * 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 (c) 2010, Oracle and/or its affiliates. All rights reserved.
 */
#include <alloca.h>

#include "libfmnotify.h"

/*ARGSUSED*/
void
nd_cleanup(nd_hdl_t *nhdl)
{
	nd_debug(nhdl, "Cleaning up ...");
	if (nhdl->nh_evhdl)
		(void) fmev_shdl_fini(nhdl->nh_evhdl);

	if (nhdl->nh_msghdl)
		fmd_msg_fini(nhdl->nh_msghdl);

	nhdl->nh_keep_running = B_FALSE;
	(void) fclose(nhdl->nh_log_fd);
}

static void
get_timestamp(char *buf, size_t bufsize)
{
	time_t utc_time;
	struct tm *p_tm;

	(void) time(&utc_time);
	p_tm = localtime(&utc_time);

	(void) strftime(buf, bufsize, "%b %d %H:%M:%S", p_tm);
}

/* PRINTFLIKE2 */
void
nd_debug(nd_hdl_t *nhdl, const char *format, ...)
{
	char timestamp[64];
	va_list ap;

	if (nhdl->nh_debug) {
		get_timestamp(timestamp, sizeof (timestamp));
		(void) fprintf(nhdl->nh_log_fd, "[ %s ", timestamp);
		va_start(ap, format);
		(void) vfprintf(nhdl->nh_log_fd, format, ap);
		va_end(ap);
		(void) fprintf(nhdl->nh_log_fd, " ]\n");
	}
	(void) fflush(nhdl->nh_log_fd);
}

void
nd_dump_nvlist(nd_hdl_t *nhdl, nvlist_t *nvl)
{
	if (nhdl->nh_debug)
		nvlist_print(nhdl->nh_log_fd, nvl);
}

/* PRINTFLIKE2 */
void
nd_error(nd_hdl_t *nhdl, const char *format, ...)
{
	char timestamp[64];
	va_list ap;

	get_timestamp(timestamp, sizeof (timestamp));
	(void) fprintf(nhdl->nh_log_fd, "[ %s ", timestamp);
	va_start(ap, format);
	(void) vfprintf(nhdl->nh_log_fd, format, ap);
	va_end(ap);
	(void) fprintf(nhdl->nh_log_fd, " ]\n");
	(void) fflush(nhdl->nh_log_fd);
}

/* PRINTFLIKE2 */
void
nd_abort(nd_hdl_t *nhdl, const char *format, ...)
{
	char timestamp[64];
	va_list ap;

	get_timestamp(timestamp, sizeof (timestamp));
	(void) fprintf(nhdl->nh_log_fd, "[ %s ", timestamp);
	va_start(ap, format);
	(void) vfprintf(nhdl->nh_log_fd, format, ap);
	va_end(ap);
	(void) fprintf(nhdl->nh_log_fd, " ]\n");
	(void) fflush(nhdl->nh_log_fd);
	nd_cleanup(nhdl);
}

void
nd_daemonize(nd_hdl_t *nhdl)
{
	pid_t pid;

	if ((pid = fork()) < 0)
		nd_abort(nhdl, "Failed to fork child (%s)", strerror(errno));
	else if (pid > 0)
		exit(0);

	(void) setsid();
	(void) close(0);
	(void) close(1);
	/*
	 * We leave stderr open so we can write debug/err messages to the SMF
	 * service log
	 */
	nhdl->nh_is_daemon = B_TRUE;
}

/*
 * This function returns a pointer to the specified SMF property group for the
 * specified SMF service.  The caller is responsible for freeing the property
 * group.  On failure, the function returns NULL.
 */
static scf_propertygroup_t *
nd_get_pg(nd_hdl_t *nhdl, scf_handle_t *handle, const char *svcname,
    const char *pgname)
{
	scf_scope_t *sc = NULL;
	scf_service_t *svc = NULL;
	scf_propertygroup_t *pg = NULL, *ret = NULL;

	sc = scf_scope_create(handle);
	svc = scf_service_create(handle);
	pg = scf_pg_create(handle);

	if (sc == NULL || svc == NULL || pg == NULL) {
		nd_error(nhdl, "Failed to allocate libscf structures");
		scf_pg_destroy(pg);
		goto get_pg_done;
	}

	if (scf_handle_bind(handle) != -1 &&
	    scf_handle_get_scope(handle, SCF_SCOPE_LOCAL, sc) != -1 &&
	    scf_scope_get_service(sc, svcname, svc) != -1 &&
	    scf_service_get_pg(svc, pgname, pg) != -1)
		ret = pg;
	else
		scf_pg_destroy(pg);

get_pg_done:
	scf_service_destroy(svc);
	scf_scope_destroy(sc);

	return (ret);
}

int
nd_get_astring_prop(nd_hdl_t *nhdl, const char *svcname, const char *pgname,
    const char *propname, char **val)
{
	scf_handle_t *handle = NULL;
	scf_propertygroup_t *pg;
	scf_property_t *prop = NULL;
	scf_value_t *value = NULL;
	char strval[255];
	int ret = -1;

	if ((handle = scf_handle_create(SCF_VERSION)) == NULL)
		return (ret);

	if ((pg = nd_get_pg(nhdl, handle, svcname, pgname)) == NULL) {
		nd_error(nhdl, "Failed to read retrieve %s "
		    "property group for %s", pgname, svcname);
		goto astring_done;
	}
	prop = scf_property_create(handle);
	value = scf_value_create(handle);
	if (prop == NULL || value == NULL) {
		nd_error(nhdl, "Failed to allocate SMF structures");
		goto astring_done;
	}
	if (scf_pg_get_property(pg, propname, prop) == -1 ||
	    scf_property_get_value(prop, value) == -1 ||
	    scf_value_get_astring(value, strval, 255) == -1) {
		nd_error(nhdl, "Failed to retrieve %s prop (%s)", propname,
		    scf_strerror(scf_error()));
		goto astring_done;
	}
	*val = strdup(strval);
	ret = 0;

astring_done:
	scf_value_destroy(value);
	scf_property_destroy(prop);
	scf_pg_destroy(pg);
	scf_handle_destroy(handle);

	return (ret);
}

int
nd_get_boolean_prop(nd_hdl_t *nhdl, const char *svcname, const char *pgname,
    const char *propname, uint8_t *val)
{
	scf_handle_t *handle = NULL;
	scf_propertygroup_t *pg;
	scf_property_t *prop = NULL;
	scf_value_t *value = NULL;
	int ret = -1;

	if ((handle = scf_handle_create(SCF_VERSION)) == NULL)
		return (ret);

	if ((pg = nd_get_pg(nhdl, handle, svcname, pgname)) == NULL) {
		nd_error(nhdl, "Failed to read retrieve %s "
		    "property group for %s", pgname, svcname);
		goto bool_done;
	}
	prop = scf_property_create(handle);
	value = scf_value_create(handle);
	if (prop == NULL || value == NULL) {
		nd_error(nhdl, "Failed to allocate SMF structures");
		goto bool_done;
	}
	if (scf_pg_get_property(pg, propname, prop) == -1 ||
	    scf_property_get_value(prop, value) == -1 ||
	    scf_value_get_boolean(value, val) == -1) {
		nd_error(nhdl, "Failed to retrieve %s prop (%s)", propname,
		    scf_strerror(scf_error()));
		goto bool_done;
	}
	ret = 0;

bool_done:
	scf_value_destroy(value);
	scf_property_destroy(prop);
	scf_pg_destroy(pg);
	scf_handle_destroy(handle);

	return (ret);
}

char *
nd_get_event_fmri(nd_hdl_t *nhdl, fmev_t ev)
{
	nvlist_t *ev_nvl, *attr_nvl;
	char *svcname;

	if ((ev_nvl = fmev_attr_list(ev)) == NULL) {
		nd_error(nhdl, "Failed to lookup event attr nvlist");
		return (NULL);
	}
	if (nvlist_lookup_nvlist(ev_nvl, "attr", &attr_nvl) ||
	    nvlist_lookup_string(attr_nvl, "svc-string", &svcname)) {
		nd_error(nhdl, "Malformed event 0x%p", (void *)ev_nvl);
		return (NULL);
	}

	return (strdup((const char *)svcname));
}

int
nd_get_notify_prefs(nd_hdl_t *nhdl, const char *mech, fmev_t ev,
    nvlist_t ***pref_nvl, uint_t *nprefs)
{
	nvlist_t *ev_nvl, *top_nvl, **np_nvlarr, *mech_nvl;
	int ret = 1;
	uint_t nelem;

	if ((ev_nvl = fmev_attr_list(ev)) == NULL) {
		nd_error(nhdl, "Failed to lookup event attr nvlist");
		return (-1);
	}

	if ((ret = smf_notify_get_params(&top_nvl, ev_nvl)) != SCF_SUCCESS) {
		ret = scf_error();
		if (ret == SCF_ERROR_NOT_FOUND) {
			nd_debug(nhdl, "No notification preferences specified "
			    "for this event");
			goto pref_done;
		} else {
			nd_error(nhdl, "Error looking up notification "
			    "preferences (%s)", scf_strerror(ret));
			nd_dump_nvlist(nhdl, top_nvl);
			goto pref_done;
		}
	}

	if (nvlist_lookup_nvlist_array(top_nvl, SCF_NOTIFY_PARAMS, &np_nvlarr,
	    &nelem) != 0) {
		nd_error(nhdl, "Malformed nvlist");
		nd_dump_nvlist(nhdl, top_nvl);
		ret = 1;
		goto pref_done;
	}
	*pref_nvl = malloc(nelem * sizeof (nvlist_t *));
	*nprefs = 0;

	for (int i = 0; i < nelem; i++) {
		if (nvlist_lookup_nvlist(np_nvlarr[i], mech, &mech_nvl) == 0) {
			(void) nvlist_dup(mech_nvl, *pref_nvl + *nprefs, 0);
			++*nprefs;
		}
	}

	if (*nprefs == 0) {
		nd_debug(nhdl, "No %s notification preferences specified",
		    mech);
		free(*pref_nvl);
		ret = SCF_ERROR_NOT_FOUND;
		goto pref_done;
	}
	ret = 0;
pref_done:
	nvlist_free(top_nvl);
	return (ret);
}

static int
nd_seq_search(char *key, char **list, uint_t nelem)
{
	for (int i = 0; i < nelem; i++)
		if (strcmp(key, list[i]) == 0)
			return (1);
	return (0);
}

/*
 * This function takes a single string list and splits it into
 * an string array (analogous to PERL split)
 *
 * The caller is responsible for freeing the array.
 */
int
nd_split_list(nd_hdl_t *nhdl, char *list, char *delim, char ***arr,
    uint_t *nelem)
{
	char *item, *tmpstr;
	int i = 1, size = 1;

	tmpstr = strdup(list);
	item = strtok(tmpstr, delim);
	while (item && strtok(NULL, delim) != NULL)
		size++;
	free(tmpstr);

	if ((*arr = calloc(size, sizeof (char *))) == NULL) {
		nd_error(nhdl, "Error allocating memory (%s)", strerror(errno));
		return (-1);
	}
	if (size == 1)
		(*arr)[0] = strdup(list);
	else {
		tmpstr = strdup(list);
		item = strtok(tmpstr, delim);
		(*arr)[0] = strdup(item);
		while ((item = strtok(NULL, delim)) != NULL)
			(*arr)[i++] = strdup(item);
		free(tmpstr);
	}
	*nelem = size;
	return (0);
}

/*
 * This function merges two string arrays into a single array, removing any
 * duplicates
 *
 * The caller is responsible for freeing the merged array.
 */
int
nd_merge_strarray(nd_hdl_t *nhdl, char **arr1, uint_t n1, char **arr2,
    uint_t n2, char ***buf)
{
	char **tmparr;
	int uniq = -1;

	tmparr = alloca((n1 + n2) * sizeof (char *));
	bzero(tmparr, (n1 + n2) * sizeof (char *));

	while (++uniq < n1)
		tmparr[uniq] = strdup(arr1[uniq]);

	for (int j = 0; j < n2; j++)
		if (!nd_seq_search(arr2[j], tmparr, uniq))
			tmparr[uniq++] = strdup(arr2[j]);

	if ((*buf = calloc(uniq, sizeof (char *))) == NULL) {
		nd_error(nhdl, "Error allocating memory (%s)", strerror(errno));
		for (int j = 0; j < uniq; j++) {
			if (tmparr[j])
				free(tmparr[j]);
		}
		return (-1);
	}

	bcopy(tmparr, *buf, uniq * sizeof (char *));
	return (uniq);
}

void
nd_free_strarray(char **arr, uint_t arrsz)
{
	for (uint_t i = 0; i < arrsz; i++)
		free(arr[i]);
	free(arr);
}

/*
 * This function joins all the strings in a string array into a single string
 * Each element will be delimited by a comma
 *
 * The caller is responsible for freeing the joined string.
 */
int
nd_join_strarray(nd_hdl_t *nhdl, char **arr, uint_t arrsz, char **buf)
{
	uint_t len = 0;
	char *jbuf;
	int i;

	/*
	 * First, figure out how much space we need to allocate to store the
	 * joined string.
	 */
	for (i = 0; i < arrsz; i++)
		len += strlen(arr[i]) + 1;

	if ((jbuf = calloc(len, sizeof (char))) == NULL) {
		nd_error(nhdl, "Error allocating memory (%s)", strerror(errno));
		return (-1);
	}

	(void) snprintf(jbuf, len, "%s", arr[0]);
	for (i = 1; i < arrsz; i++)
		(void) snprintf(jbuf, len, "%s,%s", jbuf, arr[i]);

	*buf = jbuf;
	return (0);
}

void
nd_free_nvlarray(nvlist_t **arr, uint_t arrsz)
{
	for (uint_t i = 0; i < arrsz; i++)
		nvlist_free(arr[i]);
	free(arr);
}

/*
 * This function takes a dictionary name and event class and then uses
 * libdiagcode to compute the MSG ID.  We need this for looking up messages
 * for the committed ireport.* events.  For FMA list.* events, the MSG ID is
 * is contained in the event payload.
 */
int
nd_get_diagcode(nd_hdl_t *nhdl, const char *dict, const char *class, char *buf,
    size_t buflen)
{
	fm_dc_handle_t *dhp;
	size_t dlen;
	char *dirpath;
	const char *key[2];
	int ret = 0;

	dlen = (strlen(nhdl->nh_rootdir) + strlen(ND_DICTDIR) + 2);
	dirpath = alloca(dlen);
	(void) snprintf(dirpath, dlen, "%s/%s", nhdl->nh_rootdir, ND_DICTDIR);

	if ((dhp = fm_dc_opendict(FM_DC_VERSION, dirpath, dict)) == NULL) {
		nd_error(nhdl, "fm_dc_opendict failed for %s/%s",
		    dirpath, dict);
		return (-1);
	}

	key[0] = class;
	key[1] = NULL;
	if (fm_dc_key2code(dhp, key, buf, buflen) < 0) {
		nd_error(nhdl, "fm_dc_key2code failed for %s", key[0]);
		ret = -1;
	}
	fm_dc_closedict(dhp);
	return (ret);
}

/*
 * This function takes an event and extracts the bits of the event payload that
 * are of interest to notification daemons and conveniently tucks them into a
 * single struct.
 *
 * The caller is responsible for freeing ev_info and any contained strings and
 * nvlists.  A convenience function, nd_free_event_info(), is provided for this
 * purpose.
 */
int
nd_get_event_info(nd_hdl_t *nhdl, const char *class, fmev_t ev,
    nd_ev_info_t **ev_info)
{
	nvlist_t *ev_nvl, *attr_nvl;
	nd_ev_info_t *evi;
	char *code, *uuid, *fmri, *from_state, *to_state, *reason;

	if ((evi = calloc(1, sizeof (nd_ev_info_t))) == NULL) {
		nd_error(nhdl, "Failed to allocate memory");
		return (-1);
	}

	/*
	 * Hold event; class and payload will be valid for as long as
	 * we hold the event.
	 */
	fmev_hold(ev);
	evi->ei_ev = ev;
	ev_nvl = fmev_attr_list(ev);

	/*
	 * Lookup the MSGID, event description and severity and KA URL
	 *
	 * For FMA list.* events we just pull it out of the the event nvlist.
	 * For all other events we call a utility function that computes the
	 * diagcode using the dict name and class.
	 */
	evi->ei_diagcode = calloc(32, sizeof (char));
	if ((nvlist_lookup_string(ev_nvl, FM_SUSPECT_DIAG_CODE, &code) == 0 &&
	    strcpy(evi->ei_diagcode, code)) ||
	    nd_get_diagcode(nhdl, "SMF", class, evi->ei_diagcode, 32)
	    == 0) {
		evi->ei_severity = fmd_msg_getitem_id(nhdl->nh_msghdl,
		    NULL, evi->ei_diagcode, FMD_MSG_ITEM_SEVERITY);
		evi->ei_descr = fmd_msg_getitem_id(nhdl->nh_msghdl,
		    NULL, evi->ei_diagcode, FMD_MSG_ITEM_DESC);
		evi->ei_url = fmd_msg_getitem_id(nhdl->nh_msghdl,
		    NULL, evi->ei_diagcode, FMD_MSG_ITEM_URL);
	} else
		(void) strcpy(evi->ei_diagcode, ND_UNKNOWN);

	if (!evi->ei_severity)
		evi->ei_severity = strdup(ND_UNKNOWN);
	if (!evi->ei_descr)
		evi->ei_descr = strdup(ND_UNKNOWN);
	if (!evi->ei_url)
		evi->ei_url = strdup(ND_UNKNOWN);

	evi->ei_payload = ev_nvl;
	evi->ei_class = fmev_class(ev);
	if (nvlist_lookup_string(ev_nvl, FM_SUSPECT_UUID, &uuid) == 0)
		evi->ei_uuid = strdup(uuid);
	else {
		nd_error(nhdl, "Malformed event");
		nd_dump_nvlist(nhdl, evi->ei_payload);
		nd_free_event_info(evi);
		return (-1);
	}

	if (strncmp(class, "ireport.os.smf", 14) == 0) {
		if ((fmri = nd_get_event_fmri(nhdl, ev)) == NULL) {
			nd_error(nhdl, "Failed to get fmri from event payload");
			nd_free_event_info(evi);
			return (-1);
		}
		if (nvlist_lookup_nvlist(evi->ei_payload, "attr", &attr_nvl) ||
		    nvlist_lookup_string(attr_nvl, "from-state", &from_state) ||
		    nvlist_lookup_string(attr_nvl, "to-state", &to_state) ||
		    nvlist_lookup_string(attr_nvl, "reason-long", &reason)) {
			nd_error(nhdl, "Malformed event");
			nd_dump_nvlist(nhdl, evi->ei_payload);
			nd_free_event_info(evi);
			free(fmri);
			return (-1);
		}
		evi->ei_fmri = fmri;
		evi->ei_to_state = strdup(to_state);
		evi->ei_from_state = strdup(from_state);
		evi->ei_reason = strdup(reason);
	}
	*ev_info = evi;
	return (0);
}

static void
condfree(void *buf)
{
	if (buf != NULL)
		free(buf);
}

void
nd_free_event_info(nd_ev_info_t *ev_info)
{
	condfree(ev_info->ei_severity);
	condfree(ev_info->ei_descr);
	condfree(ev_info->ei_diagcode);
	condfree(ev_info->ei_url);
	condfree(ev_info->ei_uuid);
	condfree(ev_info->ei_fmri);
	condfree(ev_info->ei_from_state);
	condfree(ev_info->ei_to_state);
	condfree(ev_info->ei_reason);
	fmev_rele(ev_info->ei_ev);
	free(ev_info);
}