1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
24  */
25 #include <alloca.h>
26 
27 #include "libfmnotify.h"
28 
29 /*ARGSUSED*/
30 void
nd_cleanup(nd_hdl_t * nhdl)31 nd_cleanup(nd_hdl_t *nhdl)
32 {
33 	nd_debug(nhdl, "Cleaning up ...");
34 	if (nhdl->nh_evhdl)
35 		(void) fmev_shdl_fini(nhdl->nh_evhdl);
36 
37 	if (nhdl->nh_msghdl)
38 		fmd_msg_fini(nhdl->nh_msghdl);
39 
40 	nhdl->nh_keep_running = B_FALSE;
41 	(void) fclose(nhdl->nh_log_fd);
42 }
43 
44 static void
get_timestamp(char * buf,size_t bufsize)45 get_timestamp(char *buf, size_t bufsize)
46 {
47 	time_t utc_time;
48 	struct tm *p_tm;
49 
50 	(void) time(&utc_time);
51 	p_tm = localtime(&utc_time);
52 
53 	(void) strftime(buf, bufsize, "%b %d %H:%M:%S", p_tm);
54 }
55 
56 /* PRINTFLIKE2 */
57 void
nd_debug(nd_hdl_t * nhdl,const char * format,...)58 nd_debug(nd_hdl_t *nhdl, const char *format, ...)
59 {
60 	char timestamp[64];
61 	va_list ap;
62 
63 	if (nhdl->nh_debug) {
64 		get_timestamp(timestamp, sizeof (timestamp));
65 		(void) fprintf(nhdl->nh_log_fd, "[ %s ", timestamp);
66 		va_start(ap, format);
67 		(void) vfprintf(nhdl->nh_log_fd, format, ap);
68 		va_end(ap);
69 		(void) fprintf(nhdl->nh_log_fd, " ]\n");
70 	}
71 	(void) fflush(nhdl->nh_log_fd);
72 }
73 
74 void
nd_dump_nvlist(nd_hdl_t * nhdl,nvlist_t * nvl)75 nd_dump_nvlist(nd_hdl_t *nhdl, nvlist_t *nvl)
76 {
77 	if (nhdl->nh_debug)
78 		nvlist_print(nhdl->nh_log_fd, nvl);
79 }
80 
81 /* PRINTFLIKE2 */
82 void
nd_error(nd_hdl_t * nhdl,const char * format,...)83 nd_error(nd_hdl_t *nhdl, const char *format, ...)
84 {
85 	char timestamp[64];
86 	va_list ap;
87 
88 	get_timestamp(timestamp, sizeof (timestamp));
89 	(void) fprintf(nhdl->nh_log_fd, "[ %s ", timestamp);
90 	va_start(ap, format);
91 	(void) vfprintf(nhdl->nh_log_fd, format, ap);
92 	va_end(ap);
93 	(void) fprintf(nhdl->nh_log_fd, " ]\n");
94 	(void) fflush(nhdl->nh_log_fd);
95 }
96 
97 /* PRINTFLIKE2 */
98 void
nd_abort(nd_hdl_t * nhdl,const char * format,...)99 nd_abort(nd_hdl_t *nhdl, const char *format, ...)
100 {
101 	char timestamp[64];
102 	va_list ap;
103 
104 	get_timestamp(timestamp, sizeof (timestamp));
105 	(void) fprintf(nhdl->nh_log_fd, "[ %s ", timestamp);
106 	va_start(ap, format);
107 	(void) vfprintf(nhdl->nh_log_fd, format, ap);
108 	va_end(ap);
109 	(void) fprintf(nhdl->nh_log_fd, " ]\n");
110 	(void) fflush(nhdl->nh_log_fd);
111 	nd_cleanup(nhdl);
112 }
113 
114 void
nd_daemonize(nd_hdl_t * nhdl)115 nd_daemonize(nd_hdl_t *nhdl)
116 {
117 	pid_t pid;
118 
119 	if ((pid = fork()) < 0)
120 		nd_abort(nhdl, "Failed to fork child (%s)", strerror(errno));
121 	else if (pid > 0)
122 		exit(0);
123 
124 	(void) setsid();
125 	(void) close(0);
126 	(void) close(1);
127 	/*
128 	 * We leave stderr open so we can write debug/err messages to the SMF
129 	 * service log
130 	 */
131 	nhdl->nh_is_daemon = B_TRUE;
132 }
133 
134 /*
135  * This function returns a pointer to the specified SMF property group for the
136  * specified SMF service.  The caller is responsible for freeing the property
137  * group.  On failure, the function returns NULL.
138  */
139 static scf_propertygroup_t *
nd_get_pg(nd_hdl_t * nhdl,scf_handle_t * handle,const char * svcname,const char * pgname)140 nd_get_pg(nd_hdl_t *nhdl, scf_handle_t *handle, const char *svcname,
141     const char *pgname)
142 {
143 	scf_scope_t *sc = NULL;
144 	scf_service_t *svc = NULL;
145 	scf_propertygroup_t *pg = NULL, *ret = NULL;
146 
147 	sc = scf_scope_create(handle);
148 	svc = scf_service_create(handle);
149 	pg = scf_pg_create(handle);
150 
151 	if (sc == NULL || svc == NULL || pg == NULL) {
152 		nd_error(nhdl, "Failed to allocate libscf structures");
153 		scf_pg_destroy(pg);
154 		goto get_pg_done;
155 	}
156 
157 	if (scf_handle_bind(handle) != -1 &&
158 	    scf_handle_get_scope(handle, SCF_SCOPE_LOCAL, sc) != -1 &&
159 	    scf_scope_get_service(sc, svcname, svc) != -1 &&
160 	    scf_service_get_pg(svc, pgname, pg) != -1)
161 		ret = pg;
162 	else
163 		scf_pg_destroy(pg);
164 
165 get_pg_done:
166 	scf_service_destroy(svc);
167 	scf_scope_destroy(sc);
168 
169 	return (ret);
170 }
171 
172 int
nd_get_astring_prop(nd_hdl_t * nhdl,const char * svcname,const char * pgname,const char * propname,char ** val)173 nd_get_astring_prop(nd_hdl_t *nhdl, const char *svcname, const char *pgname,
174     const char *propname, char **val)
175 {
176 	scf_handle_t *handle = NULL;
177 	scf_propertygroup_t *pg;
178 	scf_property_t *prop = NULL;
179 	scf_value_t *value = NULL;
180 	char strval[255];
181 	int ret = -1;
182 
183 	if ((handle = scf_handle_create(SCF_VERSION)) == NULL)
184 		return (ret);
185 
186 	if ((pg = nd_get_pg(nhdl, handle, svcname, pgname)) == NULL) {
187 		nd_error(nhdl, "Failed to read retrieve %s "
188 		    "property group for %s", pgname, svcname);
189 		goto astring_done;
190 	}
191 	prop = scf_property_create(handle);
192 	value = scf_value_create(handle);
193 	if (prop == NULL || value == NULL) {
194 		nd_error(nhdl, "Failed to allocate SMF structures");
195 		goto astring_done;
196 	}
197 	if (scf_pg_get_property(pg, propname, prop) == -1 ||
198 	    scf_property_get_value(prop, value) == -1 ||
199 	    scf_value_get_astring(value, strval, 255) == -1) {
200 		nd_error(nhdl, "Failed to retrieve %s prop (%s)", propname,
201 		    scf_strerror(scf_error()));
202 		goto astring_done;
203 	}
204 	*val = strdup(strval);
205 	ret = 0;
206 
207 astring_done:
208 	scf_value_destroy(value);
209 	scf_property_destroy(prop);
210 	scf_pg_destroy(pg);
211 	scf_handle_destroy(handle);
212 
213 	return (ret);
214 }
215 
216 int
nd_get_boolean_prop(nd_hdl_t * nhdl,const char * svcname,const char * pgname,const char * propname,uint8_t * val)217 nd_get_boolean_prop(nd_hdl_t *nhdl, const char *svcname, const char *pgname,
218     const char *propname, uint8_t *val)
219 {
220 	scf_handle_t *handle = NULL;
221 	scf_propertygroup_t *pg;
222 	scf_property_t *prop = NULL;
223 	scf_value_t *value = NULL;
224 	int ret = -1;
225 
226 	if ((handle = scf_handle_create(SCF_VERSION)) == NULL)
227 		return (ret);
228 
229 	if ((pg = nd_get_pg(nhdl, handle, svcname, pgname)) == NULL) {
230 		nd_error(nhdl, "Failed to read retrieve %s "
231 		    "property group for %s", pgname, svcname);
232 		goto bool_done;
233 	}
234 	prop = scf_property_create(handle);
235 	value = scf_value_create(handle);
236 	if (prop == NULL || value == NULL) {
237 		nd_error(nhdl, "Failed to allocate SMF structures");
238 		goto bool_done;
239 	}
240 	if (scf_pg_get_property(pg, propname, prop) == -1 ||
241 	    scf_property_get_value(prop, value) == -1 ||
242 	    scf_value_get_boolean(value, val) == -1) {
243 		nd_error(nhdl, "Failed to retrieve %s prop (%s)", propname,
244 		    scf_strerror(scf_error()));
245 		goto bool_done;
246 	}
247 	ret = 0;
248 
249 bool_done:
250 	scf_value_destroy(value);
251 	scf_property_destroy(prop);
252 	scf_pg_destroy(pg);
253 	scf_handle_destroy(handle);
254 
255 	return (ret);
256 }
257 
258 char *
nd_get_event_fmri(nd_hdl_t * nhdl,fmev_t ev)259 nd_get_event_fmri(nd_hdl_t *nhdl, fmev_t ev)
260 {
261 	nvlist_t *ev_nvl, *attr_nvl;
262 	char *svcname;
263 
264 	if ((ev_nvl = fmev_attr_list(ev)) == NULL) {
265 		nd_error(nhdl, "Failed to lookup event attr nvlist");
266 		return (NULL);
267 	}
268 	if (nvlist_lookup_nvlist(ev_nvl, "attr", &attr_nvl) ||
269 	    nvlist_lookup_string(attr_nvl, "svc-string", &svcname)) {
270 		nd_error(nhdl, "Malformed event 0x%p", (void *)ev_nvl);
271 		return (NULL);
272 	}
273 
274 	return (strdup((const char *)svcname));
275 }
276 
277 int
nd_get_notify_prefs(nd_hdl_t * nhdl,const char * mech,fmev_t ev,nvlist_t *** pref_nvl,uint_t * nprefs)278 nd_get_notify_prefs(nd_hdl_t *nhdl, const char *mech, fmev_t ev,
279     nvlist_t ***pref_nvl, uint_t *nprefs)
280 {
281 	nvlist_t *ev_nvl, *top_nvl, **np_nvlarr, *mech_nvl;
282 	int ret = 1;
283 	uint_t nelem;
284 
285 	if ((ev_nvl = fmev_attr_list(ev)) == NULL) {
286 		nd_error(nhdl, "Failed to lookup event attr nvlist");
287 		return (-1);
288 	}
289 
290 	if ((ret = smf_notify_get_params(&top_nvl, ev_nvl)) != SCF_SUCCESS) {
291 		ret = scf_error();
292 		if (ret == SCF_ERROR_NOT_FOUND) {
293 			nd_debug(nhdl, "No notification preferences specified "
294 			    "for this event");
295 			goto pref_done;
296 		} else {
297 			nd_error(nhdl, "Error looking up notification "
298 			    "preferences (%s)", scf_strerror(ret));
299 			nd_dump_nvlist(nhdl, top_nvl);
300 			goto pref_done;
301 		}
302 	}
303 
304 	if (nvlist_lookup_nvlist_array(top_nvl, SCF_NOTIFY_PARAMS, &np_nvlarr,
305 	    &nelem) != 0) {
306 		nd_error(nhdl, "Malformed nvlist");
307 		nd_dump_nvlist(nhdl, top_nvl);
308 		ret = 1;
309 		goto pref_done;
310 	}
311 	*pref_nvl = malloc(nelem * sizeof (nvlist_t *));
312 	*nprefs = 0;
313 
314 	for (int i = 0; i < nelem; i++) {
315 		if (nvlist_lookup_nvlist(np_nvlarr[i], mech, &mech_nvl) == 0) {
316 			(void) nvlist_dup(mech_nvl, *pref_nvl + *nprefs, 0);
317 			++*nprefs;
318 		}
319 	}
320 
321 	if (*nprefs == 0) {
322 		nd_debug(nhdl, "No %s notification preferences specified",
323 		    mech);
324 		free(*pref_nvl);
325 		ret = SCF_ERROR_NOT_FOUND;
326 		goto pref_done;
327 	}
328 	ret = 0;
329 pref_done:
330 	nvlist_free(top_nvl);
331 	return (ret);
332 }
333 
334 static int
nd_seq_search(char * key,char ** list,uint_t nelem)335 nd_seq_search(char *key, char **list, uint_t nelem)
336 {
337 	for (int i = 0; i < nelem; i++)
338 		if (strcmp(key, list[i]) == 0)
339 			return (1);
340 	return (0);
341 }
342 
343 /*
344  * This function takes a single string list and splits it into
345  * an string array (analogous to PERL split)
346  *
347  * The caller is responsible for freeing the array.
348  */
349 int
nd_split_list(nd_hdl_t * nhdl,char * list,char * delim,char *** arr,uint_t * nelem)350 nd_split_list(nd_hdl_t *nhdl, char *list, char *delim, char ***arr,
351     uint_t *nelem)
352 {
353 	char *item, *tmpstr;
354 	int i = 1, size = 1;
355 
356 	tmpstr = strdup(list);
357 	item = strtok(tmpstr, delim);
358 	while (item && strtok(NULL, delim) != NULL)
359 		size++;
360 	free(tmpstr);
361 
362 	if ((*arr = calloc(size, sizeof (char *))) == NULL) {
363 		nd_error(nhdl, "Error allocating memory (%s)", strerror(errno));
364 		return (-1);
365 	}
366 	if (size == 1)
367 		(*arr)[0] = strdup(list);
368 	else {
369 		tmpstr = strdup(list);
370 		item = strtok(tmpstr, delim);
371 		(*arr)[0] = strdup(item);
372 		while ((item = strtok(NULL, delim)) != NULL)
373 			(*arr)[i++] = strdup(item);
374 		free(tmpstr);
375 	}
376 	*nelem = size;
377 	return (0);
378 }
379 
380 /*
381  * This function merges two string arrays into a single array, removing any
382  * duplicates
383  *
384  * The caller is responsible for freeing the merged array.
385  */
386 int
nd_merge_strarray(nd_hdl_t * nhdl,char ** arr1,uint_t n1,char ** arr2,uint_t n2,char *** buf)387 nd_merge_strarray(nd_hdl_t *nhdl, char **arr1, uint_t n1, char **arr2,
388     uint_t n2, char ***buf)
389 {
390 	char **tmparr;
391 	int uniq = -1;
392 
393 	tmparr = alloca((n1 + n2) * sizeof (char *));
394 	bzero(tmparr, (n1 + n2) * sizeof (char *));
395 
396 	while (++uniq < n1)
397 		tmparr[uniq] = strdup(arr1[uniq]);
398 
399 	for (int j = 0; j < n2; j++)
400 		if (!nd_seq_search(arr2[j], tmparr, uniq))
401 			tmparr[uniq++] = strdup(arr2[j]);
402 
403 	if ((*buf = calloc(uniq, sizeof (char *))) == NULL) {
404 		nd_error(nhdl, "Error allocating memory (%s)", strerror(errno));
405 		for (int j = 0; j < uniq; j++) {
406 			if (tmparr[j])
407 				free(tmparr[j]);
408 		}
409 		return (-1);
410 	}
411 
412 	bcopy(tmparr, *buf, uniq * sizeof (char *));
413 	return (uniq);
414 }
415 
416 void
nd_free_strarray(char ** arr,uint_t arrsz)417 nd_free_strarray(char **arr, uint_t arrsz)
418 {
419 	for (uint_t i = 0; i < arrsz; i++)
420 		free(arr[i]);
421 	free(arr);
422 }
423 
424 /*
425  * This function joins all the strings in a string array into a single string
426  * Each element will be delimited by a comma
427  *
428  * The caller is responsible for freeing the joined string.
429  */
430 int
nd_join_strarray(nd_hdl_t * nhdl,char ** arr,uint_t arrsz,char ** buf)431 nd_join_strarray(nd_hdl_t *nhdl, char **arr, uint_t arrsz, char **buf)
432 {
433 	uint_t len = 0;
434 	char *jbuf;
435 	int i;
436 
437 	/*
438 	 * First, figure out how much space we need to allocate to store the
439 	 * joined string.
440 	 */
441 	for (i = 0; i < arrsz; i++)
442 		len += strlen(arr[i]) + 1;
443 
444 	if ((jbuf = calloc(len, sizeof (char))) == NULL) {
445 		nd_error(nhdl, "Error allocating memory (%s)", strerror(errno));
446 		return (-1);
447 	}
448 
449 	(void) snprintf(jbuf, len, "%s", arr[0]);
450 	for (i = 1; i < arrsz; i++) {
451 		(void) strlcat(jbuf, ",", len);
452 		(void) strlcat(jbuf, arr[i], len);
453 	}
454 
455 	*buf = jbuf;
456 	return (0);
457 }
458 
459 void
nd_free_nvlarray(nvlist_t ** arr,uint_t arrsz)460 nd_free_nvlarray(nvlist_t **arr, uint_t arrsz)
461 {
462 	for (uint_t i = 0; i < arrsz; i++)
463 		nvlist_free(arr[i]);
464 	free(arr);
465 }
466 
467 /*
468  * This function takes a dictionary name and event class and then uses
469  * libdiagcode to compute the MSG ID.  We need this for looking up messages
470  * for the committed ireport.* events.  For FMA list.* events, the MSG ID is
471  * is contained in the event payload.
472  */
473 int
nd_get_diagcode(nd_hdl_t * nhdl,const char * dict,const char * class,char * buf,size_t buflen)474 nd_get_diagcode(nd_hdl_t *nhdl, const char *dict, const char *class, char *buf,
475     size_t buflen)
476 {
477 	fm_dc_handle_t *dhp;
478 	size_t dlen;
479 	char *dirpath;
480 	const char *key[2];
481 	int ret = 0;
482 
483 	dlen = (strlen(nhdl->nh_rootdir) + strlen(ND_DICTDIR) + 2);
484 	dirpath = alloca(dlen);
485 	(void) snprintf(dirpath, dlen, "%s/%s", nhdl->nh_rootdir, ND_DICTDIR);
486 
487 	if ((dhp = fm_dc_opendict(FM_DC_VERSION, dirpath, dict)) == NULL) {
488 		nd_error(nhdl, "fm_dc_opendict failed for %s/%s",
489 		    dirpath, dict);
490 		return (-1);
491 	}
492 
493 	key[0] = class;
494 	key[1] = NULL;
495 	if (fm_dc_key2code(dhp, key, buf, buflen) < 0) {
496 		nd_error(nhdl, "fm_dc_key2code failed for %s", key[0]);
497 		ret = -1;
498 	}
499 	fm_dc_closedict(dhp);
500 	return (ret);
501 }
502 
503 /*
504  * This function takes an event and extracts the bits of the event payload that
505  * are of interest to notification daemons and conveniently tucks them into a
506  * single struct.
507  *
508  * The caller is responsible for freeing ev_info and any contained strings and
509  * nvlists.  A convenience function, nd_free_event_info(), is provided for this
510  * purpose.
511  */
512 int
nd_get_event_info(nd_hdl_t * nhdl,const char * class,fmev_t ev,nd_ev_info_t ** ev_info)513 nd_get_event_info(nd_hdl_t *nhdl, const char *class, fmev_t ev,
514     nd_ev_info_t **ev_info)
515 {
516 	nvlist_t *ev_nvl, *attr_nvl;
517 	nd_ev_info_t *evi;
518 	char *code, *uuid, *fmri, *from_state, *to_state, *reason;
519 
520 	if ((evi = calloc(1, sizeof (nd_ev_info_t))) == NULL) {
521 		nd_error(nhdl, "Failed to allocate memory");
522 		return (-1);
523 	}
524 
525 	/*
526 	 * Hold event; class and payload will be valid for as long as
527 	 * we hold the event.
528 	 */
529 	fmev_hold(ev);
530 	evi->ei_ev = ev;
531 	ev_nvl = fmev_attr_list(ev);
532 
533 	/*
534 	 * Lookup the MSGID, event description and severity and KA URL
535 	 *
536 	 * For FMA list.* events we just pull it out of the the event nvlist.
537 	 * For all other events we call a utility function that computes the
538 	 * diagcode using the dict name and class.
539 	 */
540 	evi->ei_diagcode = calloc(32, sizeof (char));
541 	if ((nvlist_lookup_string(ev_nvl, FM_SUSPECT_DIAG_CODE, &code) == 0 &&
542 	    strcpy(evi->ei_diagcode, code)) ||
543 	    nd_get_diagcode(nhdl, "SMF", class, evi->ei_diagcode, 32)
544 	    == 0) {
545 		evi->ei_severity = fmd_msg_getitem_id(nhdl->nh_msghdl,
546 		    NULL, evi->ei_diagcode, FMD_MSG_ITEM_SEVERITY);
547 		evi->ei_descr = fmd_msg_getitem_id(nhdl->nh_msghdl,
548 		    NULL, evi->ei_diagcode, FMD_MSG_ITEM_DESC);
549 		evi->ei_url = fmd_msg_getitem_id(nhdl->nh_msghdl,
550 		    NULL, evi->ei_diagcode, FMD_MSG_ITEM_URL);
551 	} else
552 		(void) strcpy(evi->ei_diagcode, ND_UNKNOWN);
553 
554 	if (!evi->ei_severity)
555 		evi->ei_severity = strdup(ND_UNKNOWN);
556 	if (!evi->ei_descr)
557 		evi->ei_descr = strdup(ND_UNKNOWN);
558 	if (!evi->ei_url)
559 		evi->ei_url = strdup(ND_UNKNOWN);
560 
561 	evi->ei_payload = ev_nvl;
562 	evi->ei_class = fmev_class(ev);
563 	if (nvlist_lookup_string(ev_nvl, FM_SUSPECT_UUID, &uuid) == 0)
564 		evi->ei_uuid = strdup(uuid);
565 	else {
566 		nd_error(nhdl, "Malformed event");
567 		nd_dump_nvlist(nhdl, evi->ei_payload);
568 		nd_free_event_info(evi);
569 		return (-1);
570 	}
571 
572 	if (strncmp(class, "ireport.os.smf", 14) == 0) {
573 		if ((fmri = nd_get_event_fmri(nhdl, ev)) == NULL) {
574 			nd_error(nhdl, "Failed to get fmri from event payload");
575 			nd_free_event_info(evi);
576 			return (-1);
577 		}
578 		if (nvlist_lookup_nvlist(evi->ei_payload, "attr", &attr_nvl) ||
579 		    nvlist_lookup_string(attr_nvl, "from-state", &from_state) ||
580 		    nvlist_lookup_string(attr_nvl, "to-state", &to_state) ||
581 		    nvlist_lookup_string(attr_nvl, "reason-long", &reason)) {
582 			nd_error(nhdl, "Malformed event");
583 			nd_dump_nvlist(nhdl, evi->ei_payload);
584 			nd_free_event_info(evi);
585 			free(fmri);
586 			return (-1);
587 		}
588 		evi->ei_fmri = fmri;
589 		evi->ei_to_state = strdup(to_state);
590 		evi->ei_from_state = strdup(from_state);
591 		evi->ei_reason = strdup(reason);
592 	}
593 	*ev_info = evi;
594 	return (0);
595 }
596 
597 void
nd_free_event_info(nd_ev_info_t * ev_info)598 nd_free_event_info(nd_ev_info_t *ev_info)
599 {
600 	free(ev_info->ei_severity);
601 	free(ev_info->ei_descr);
602 	free(ev_info->ei_diagcode);
603 	free(ev_info->ei_url);
604 	free(ev_info->ei_uuid);
605 	free(ev_info->ei_fmri);
606 	free(ev_info->ei_from_state);
607 	free(ev_info->ei_to_state);
608 	free(ev_info->ei_reason);
609 	fmev_rele(ev_info->ei_ev);
610 	free(ev_info);
611 }
612