xref: /freebsd/sys/contrib/openzfs/cmd/zed/agents/fmd_api.c (revision 924226fba12cc9a228c73b956e1b7fa24c60b055)
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  * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
23  *
24  * Copyright (c) 2016, Intel Corporation.
25  */
26 
27 /*
28  * This file implements the minimal FMD module API required to support the
29  * fault logic modules in ZED. This support includes module registration,
30  * memory allocation, module property accessors, basic case management,
31  * one-shot timers and SERD engines.
32  *
33  * In the ZED runtime, the modules are called from a single thread so no
34  * locking is required in this emulated FMD environment.
35  */
36 
37 #include <sys/types.h>
38 #include <sys/fm/protocol.h>
39 #include <uuid/uuid.h>
40 #include <signal.h>
41 #include <string.h>
42 #include <time.h>
43 
44 #include "fmd_api.h"
45 #include "fmd_serd.h"
46 
47 #include "zfs_agents.h"
48 #include "../zed_log.h"
49 
50 typedef struct fmd_modstat {
51 	fmd_stat_t	ms_accepted;	/* total events accepted by module */
52 	fmd_stat_t	ms_caseopen;	/* cases currently open */
53 	fmd_stat_t	ms_casesolved;	/* total cases solved by module */
54 	fmd_stat_t	ms_caseclosed;	/* total cases closed by module */
55 } fmd_modstat_t;
56 
57 typedef struct fmd_module {
58 	const char	*mod_name;	/* basename of module (ro) */
59 	const fmd_hdl_info_t *mod_info;	/* module info registered with handle */
60 	void		*mod_spec;	/* fmd_hdl_get/setspecific data value */
61 	fmd_stat_t	*mod_ustat;	/* module specific custom stats */
62 	uint_t		mod_ustat_cnt;	/* count of ustat stats */
63 	fmd_modstat_t	mod_stats;	/* fmd built-in per-module statistics */
64 	fmd_serd_hash_t	mod_serds;	/* hash of serd engs owned by module */
65 	char		*mod_vers;	/* a copy of module version string */
66 } fmd_module_t;
67 
68 /*
69  * ZED has two FMD hardwired module instances
70  */
71 fmd_module_t	zfs_retire_module;
72 fmd_module_t	zfs_diagnosis_module;
73 
74 /*
75  * Enable a reasonable set of defaults for libumem debugging on DEBUG builds.
76  */
77 
78 #ifdef DEBUG
79 const char *
80 _umem_debug_init(void)
81 {
82 	return ("default,verbose"); /* $UMEM_DEBUG setting */
83 }
84 
85 const char *
86 _umem_logging_init(void)
87 {
88 	return ("fail,contents"); /* $UMEM_LOGGING setting */
89 }
90 #endif
91 
92 /*
93  * Register a module with fmd and finish module initialization.
94  * Returns an integer indicating whether it succeeded (zero) or
95  * failed (non-zero).
96  */
97 int
98 fmd_hdl_register(fmd_hdl_t *hdl, int version, const fmd_hdl_info_t *mip)
99 {
100 	(void) version;
101 	fmd_module_t *mp = (fmd_module_t *)hdl;
102 
103 	mp->mod_info = mip;
104 	mp->mod_name = mip->fmdi_desc + 4;	/* drop 'ZFS ' prefix */
105 	mp->mod_spec = NULL;
106 
107 	/* bare minimum module stats */
108 	(void) strcpy(mp->mod_stats.ms_accepted.fmds_name, "fmd.accepted");
109 	(void) strcpy(mp->mod_stats.ms_caseopen.fmds_name, "fmd.caseopen");
110 	(void) strcpy(mp->mod_stats.ms_casesolved.fmds_name, "fmd.casesolved");
111 	(void) strcpy(mp->mod_stats.ms_caseclosed.fmds_name, "fmd.caseclosed");
112 
113 	fmd_serd_hash_create(&mp->mod_serds);
114 
115 	fmd_hdl_debug(hdl, "register module");
116 
117 	return (0);
118 }
119 
120 void
121 fmd_hdl_unregister(fmd_hdl_t *hdl)
122 {
123 	fmd_module_t *mp = (fmd_module_t *)hdl;
124 	fmd_modstat_t *msp = &mp->mod_stats;
125 	const fmd_hdl_ops_t *ops = mp->mod_info->fmdi_ops;
126 
127 	/* dump generic module stats */
128 	fmd_hdl_debug(hdl, "%s: %llu", msp->ms_accepted.fmds_name,
129 	    msp->ms_accepted.fmds_value.ui64);
130 	if (ops->fmdo_close != NULL) {
131 		fmd_hdl_debug(hdl, "%s: %llu", msp->ms_caseopen.fmds_name,
132 		    msp->ms_caseopen.fmds_value.ui64);
133 		fmd_hdl_debug(hdl, "%s: %llu", msp->ms_casesolved.fmds_name,
134 		    msp->ms_casesolved.fmds_value.ui64);
135 		fmd_hdl_debug(hdl, "%s: %llu", msp->ms_caseclosed.fmds_name,
136 		    msp->ms_caseclosed.fmds_value.ui64);
137 	}
138 
139 	/* dump module specific stats */
140 	if (mp->mod_ustat != NULL) {
141 		int i;
142 
143 		for (i = 0; i < mp->mod_ustat_cnt; i++) {
144 			fmd_hdl_debug(hdl, "%s: %llu",
145 			    mp->mod_ustat[i].fmds_name,
146 			    mp->mod_ustat[i].fmds_value.ui64);
147 		}
148 	}
149 
150 	fmd_serd_hash_destroy(&mp->mod_serds);
151 
152 	fmd_hdl_debug(hdl, "unregister module");
153 }
154 
155 /*
156  * fmd_hdl_setspecific() is used to associate a data pointer with
157  * the specified handle for the duration of the module's lifetime.
158  * This pointer can be retrieved using fmd_hdl_getspecific().
159  */
160 void
161 fmd_hdl_setspecific(fmd_hdl_t *hdl, void *spec)
162 {
163 	fmd_module_t *mp = (fmd_module_t *)hdl;
164 
165 	mp->mod_spec = spec;
166 }
167 
168 /*
169  * Return the module-specific data pointer previously associated
170  * with the handle using fmd_hdl_setspecific().
171  */
172 void *
173 fmd_hdl_getspecific(fmd_hdl_t *hdl)
174 {
175 	fmd_module_t *mp = (fmd_module_t *)hdl;
176 
177 	return (mp->mod_spec);
178 }
179 
180 void *
181 fmd_hdl_alloc(fmd_hdl_t *hdl, size_t size, int flags)
182 {
183 	(void) hdl;
184 	return (umem_alloc(size, flags));
185 }
186 
187 void *
188 fmd_hdl_zalloc(fmd_hdl_t *hdl, size_t size, int flags)
189 {
190 	(void) hdl;
191 	return (umem_zalloc(size, flags));
192 }
193 
194 void
195 fmd_hdl_free(fmd_hdl_t *hdl, void *data, size_t size)
196 {
197 	(void) hdl;
198 	umem_free(data, size);
199 }
200 
201 /*
202  * Record a module debug message using the specified format.
203  */
204 void
205 fmd_hdl_debug(fmd_hdl_t *hdl, const char *format, ...)
206 {
207 	char message[256];
208 	va_list vargs;
209 	fmd_module_t *mp = (fmd_module_t *)hdl;
210 
211 	va_start(vargs, format);
212 	(void) vsnprintf(message, sizeof (message), format, vargs);
213 	va_end(vargs);
214 
215 	/* prefix message with module name */
216 	zed_log_msg(LOG_INFO, "%s: %s", mp->mod_name, message);
217 }
218 
219 /* Property Retrieval */
220 
221 int32_t
222 fmd_prop_get_int32(fmd_hdl_t *hdl, const char *name)
223 {
224 	(void) hdl;
225 
226 	/*
227 	 * These can be looked up in mp->modinfo->fmdi_props
228 	 * For now we just hard code for phase 2. In the
229 	 * future, there can be a ZED based override.
230 	 */
231 	if (strcmp(name, "spare_on_remove") == 0)
232 		return (1);
233 
234 	if (strcmp(name, "io_N") == 0 || strcmp(name, "checksum_N") == 0)
235 		return (10);	/* N = 10 events */
236 
237 	return (0);
238 }
239 
240 int64_t
241 fmd_prop_get_int64(fmd_hdl_t *hdl, const char *name)
242 {
243 	(void) hdl;
244 
245 	/*
246 	 * These can be looked up in mp->modinfo->fmdi_props
247 	 * For now we just hard code for phase 2. In the
248 	 * future, there can be a ZED based override.
249 	 */
250 	if (strcmp(name, "remove_timeout") == 0)
251 		return (15ULL * 1000ULL * 1000ULL * 1000ULL);	/* 15 sec */
252 
253 	if (strcmp(name, "io_T") == 0 || strcmp(name, "checksum_T") == 0)
254 		return (1000ULL * 1000ULL * 1000ULL * 600ULL);	/* 10 min */
255 
256 	return (0);
257 }
258 
259 /* FMD Statistics */
260 
261 fmd_stat_t *
262 fmd_stat_create(fmd_hdl_t *hdl, uint_t flags, uint_t nstats, fmd_stat_t *statv)
263 {
264 	fmd_module_t *mp = (fmd_module_t *)hdl;
265 
266 	if (flags == FMD_STAT_NOALLOC) {
267 		mp->mod_ustat = statv;
268 		mp->mod_ustat_cnt = nstats;
269 	}
270 
271 	return (statv);
272 }
273 
274 /* Case Management */
275 
276 fmd_case_t *
277 fmd_case_open(fmd_hdl_t *hdl, void *data)
278 {
279 	fmd_module_t *mp = (fmd_module_t *)hdl;
280 	uuid_t uuid;
281 
282 	fmd_case_t *cp;
283 
284 	cp = fmd_hdl_zalloc(hdl, sizeof (fmd_case_t), FMD_SLEEP);
285 	cp->ci_mod = hdl;
286 	cp->ci_state = FMD_CASE_UNSOLVED;
287 	cp->ci_flags = FMD_CF_DIRTY;
288 	cp->ci_data = data;
289 	cp->ci_bufptr = NULL;
290 	cp->ci_bufsiz = 0;
291 
292 	uuid_generate(uuid);
293 	uuid_unparse(uuid, cp->ci_uuid);
294 
295 	fmd_hdl_debug(hdl, "case opened (%s)", cp->ci_uuid);
296 	mp->mod_stats.ms_caseopen.fmds_value.ui64++;
297 
298 	return (cp);
299 }
300 
301 void
302 fmd_case_solve(fmd_hdl_t *hdl, fmd_case_t *cp)
303 {
304 	fmd_module_t *mp = (fmd_module_t *)hdl;
305 
306 	/*
307 	 * For ZED, the event was already sent from fmd_case_add_suspect()
308 	 */
309 
310 	if (cp->ci_state >= FMD_CASE_SOLVED)
311 		fmd_hdl_debug(hdl, "case is already solved or closed");
312 
313 	cp->ci_state = FMD_CASE_SOLVED;
314 
315 	fmd_hdl_debug(hdl, "case solved (%s)", cp->ci_uuid);
316 	mp->mod_stats.ms_casesolved.fmds_value.ui64++;
317 }
318 
319 void
320 fmd_case_close(fmd_hdl_t *hdl, fmd_case_t *cp)
321 {
322 	fmd_module_t *mp = (fmd_module_t *)hdl;
323 	const fmd_hdl_ops_t *ops = mp->mod_info->fmdi_ops;
324 
325 	fmd_hdl_debug(hdl, "case closed (%s)", cp->ci_uuid);
326 
327 	if (ops->fmdo_close != NULL)
328 		ops->fmdo_close(hdl, cp);
329 
330 	mp->mod_stats.ms_caseopen.fmds_value.ui64--;
331 	mp->mod_stats.ms_caseclosed.fmds_value.ui64++;
332 
333 	if (cp->ci_bufptr != NULL && cp->ci_bufsiz > 0)
334 		fmd_hdl_free(hdl, cp->ci_bufptr, cp->ci_bufsiz);
335 
336 	fmd_hdl_free(hdl, cp, sizeof (fmd_case_t));
337 }
338 
339 void
340 fmd_case_uuresolved(fmd_hdl_t *hdl, const char *uuid)
341 {
342 	fmd_hdl_debug(hdl, "case resolved by uuid (%s)", uuid);
343 }
344 
345 boolean_t
346 fmd_case_solved(fmd_hdl_t *hdl, fmd_case_t *cp)
347 {
348 	(void) hdl;
349 	return (cp->ci_state >= FMD_CASE_SOLVED);
350 }
351 
352 void
353 fmd_case_add_ereport(fmd_hdl_t *hdl, fmd_case_t *cp, fmd_event_t *ep)
354 {
355 	(void) hdl, (void) cp, (void) ep;
356 }
357 
358 static void
359 zed_log_fault(nvlist_t *nvl, const char *uuid, const char *code)
360 {
361 	nvlist_t *rsrc;
362 	char *strval;
363 	uint64_t guid;
364 	uint8_t byte;
365 
366 	zed_log_msg(LOG_INFO, "\nzed_fault_event:");
367 
368 	if (uuid != NULL)
369 		zed_log_msg(LOG_INFO, "\t%s: %s", FM_SUSPECT_UUID, uuid);
370 	if (nvlist_lookup_string(nvl, FM_CLASS, &strval) == 0)
371 		zed_log_msg(LOG_INFO, "\t%s: %s", FM_CLASS, strval);
372 	if (code != NULL)
373 		zed_log_msg(LOG_INFO, "\t%s: %s", FM_SUSPECT_DIAG_CODE, code);
374 	if (nvlist_lookup_uint8(nvl, FM_FAULT_CERTAINTY, &byte) == 0)
375 		zed_log_msg(LOG_INFO, "\t%s: %llu", FM_FAULT_CERTAINTY, byte);
376 	if (nvlist_lookup_nvlist(nvl, FM_FAULT_RESOURCE, &rsrc) == 0) {
377 		if (nvlist_lookup_string(rsrc, FM_FMRI_SCHEME, &strval) == 0)
378 			zed_log_msg(LOG_INFO, "\t%s: %s", FM_FMRI_SCHEME,
379 			    strval);
380 		if (nvlist_lookup_uint64(rsrc, FM_FMRI_ZFS_POOL, &guid) == 0)
381 			zed_log_msg(LOG_INFO, "\t%s: %llu", FM_FMRI_ZFS_POOL,
382 			    guid);
383 		if (nvlist_lookup_uint64(rsrc, FM_FMRI_ZFS_VDEV, &guid) == 0)
384 			zed_log_msg(LOG_INFO, "\t%s: %llu \n", FM_FMRI_ZFS_VDEV,
385 			    guid);
386 	}
387 }
388 
389 static const char *
390 fmd_fault_mkcode(nvlist_t *fault)
391 {
392 	char *class, *code = "-";
393 
394 	/*
395 	 * Note: message codes come from: openzfs/usr/src/cmd/fm/dicts/ZFS.po
396 	 */
397 	if (nvlist_lookup_string(fault, FM_CLASS, &class) == 0) {
398 		if (strcmp(class, "fault.fs.zfs.vdev.io") == 0)
399 			code = "ZFS-8000-FD";
400 		else if (strcmp(class, "fault.fs.zfs.vdev.checksum") == 0)
401 			code = "ZFS-8000-GH";
402 		else if (strcmp(class, "fault.fs.zfs.io_failure_wait") == 0)
403 			code = "ZFS-8000-HC";
404 		else if (strcmp(class, "fault.fs.zfs.io_failure_continue") == 0)
405 			code = "ZFS-8000-JQ";
406 		else if (strcmp(class, "fault.fs.zfs.log_replay") == 0)
407 			code = "ZFS-8000-K4";
408 		else if (strcmp(class, "fault.fs.zfs.pool") == 0)
409 			code = "ZFS-8000-CS";
410 		else if (strcmp(class, "fault.fs.zfs.device") == 0)
411 			code = "ZFS-8000-D3";
412 
413 	}
414 	return (code);
415 }
416 
417 void
418 fmd_case_add_suspect(fmd_hdl_t *hdl, fmd_case_t *cp, nvlist_t *fault)
419 {
420 	nvlist_t *nvl;
421 	const char *code = fmd_fault_mkcode(fault);
422 	int64_t tod[2];
423 	int err = 0;
424 
425 	/*
426 	 * payload derived from fmd_protocol_list()
427 	 */
428 
429 	(void) gettimeofday(&cp->ci_tv, NULL);
430 	tod[0] = cp->ci_tv.tv_sec;
431 	tod[1] = cp->ci_tv.tv_usec;
432 
433 	nvl = fmd_nvl_alloc(hdl, FMD_SLEEP);
434 
435 	err |= nvlist_add_uint8(nvl, FM_VERSION, FM_SUSPECT_VERSION);
436 	err |= nvlist_add_string(nvl, FM_CLASS, FM_LIST_SUSPECT_CLASS);
437 	err |= nvlist_add_string(nvl, FM_SUSPECT_UUID, cp->ci_uuid);
438 	err |= nvlist_add_string(nvl, FM_SUSPECT_DIAG_CODE, code);
439 	err |= nvlist_add_int64_array(nvl, FM_SUSPECT_DIAG_TIME, tod, 2);
440 	err |= nvlist_add_uint32(nvl, FM_SUSPECT_FAULT_SZ, 1);
441 	err |= nvlist_add_nvlist_array(nvl, FM_SUSPECT_FAULT_LIST,
442 	    (const nvlist_t **)&fault, 1);
443 
444 	if (err)
445 		zed_log_die("failed to populate nvlist");
446 
447 	zed_log_fault(fault, cp->ci_uuid, code);
448 	zfs_agent_post_event(FM_LIST_SUSPECT_CLASS, NULL, nvl);
449 
450 	nvlist_free(nvl);
451 	nvlist_free(fault);
452 }
453 
454 void
455 fmd_case_setspecific(fmd_hdl_t *hdl, fmd_case_t *cp, void *data)
456 {
457 	(void) hdl;
458 	cp->ci_data = data;
459 }
460 
461 void *
462 fmd_case_getspecific(fmd_hdl_t *hdl, fmd_case_t *cp)
463 {
464 	(void) hdl;
465 	return (cp->ci_data);
466 }
467 
468 void
469 fmd_buf_create(fmd_hdl_t *hdl, fmd_case_t *cp, const char *name, size_t size)
470 {
471 	assert(strcmp(name, "data") == 0), (void) name;
472 	assert(cp->ci_bufptr == NULL);
473 	assert(size < (1024 * 1024));
474 
475 	cp->ci_bufptr = fmd_hdl_alloc(hdl, size, FMD_SLEEP);
476 	cp->ci_bufsiz = size;
477 }
478 
479 void
480 fmd_buf_read(fmd_hdl_t *hdl, fmd_case_t *cp,
481     const char *name, void *buf, size_t size)
482 {
483 	(void) hdl;
484 	assert(strcmp(name, "data") == 0), (void) name;
485 	assert(cp->ci_bufptr != NULL);
486 	assert(size <= cp->ci_bufsiz);
487 
488 	memcpy(buf, cp->ci_bufptr, size);
489 }
490 
491 void
492 fmd_buf_write(fmd_hdl_t *hdl, fmd_case_t *cp,
493     const char *name, const void *buf, size_t size)
494 {
495 	(void) hdl;
496 	assert(strcmp(name, "data") == 0), (void) name;
497 	assert(cp->ci_bufptr != NULL);
498 	assert(cp->ci_bufsiz >= size);
499 
500 	memcpy(cp->ci_bufptr, buf, size);
501 }
502 
503 /* SERD Engines */
504 
505 void
506 fmd_serd_create(fmd_hdl_t *hdl, const char *name, uint_t n, hrtime_t t)
507 {
508 	fmd_module_t *mp = (fmd_module_t *)hdl;
509 
510 	if (fmd_serd_eng_lookup(&mp->mod_serds, name) != NULL) {
511 		zed_log_msg(LOG_ERR, "failed to create SERD engine '%s': "
512 		    " name already exists", name);
513 		return;
514 	}
515 
516 	(void) fmd_serd_eng_insert(&mp->mod_serds, name, n, t);
517 }
518 
519 void
520 fmd_serd_destroy(fmd_hdl_t *hdl, const char *name)
521 {
522 	fmd_module_t *mp = (fmd_module_t *)hdl;
523 
524 	fmd_serd_eng_delete(&mp->mod_serds, name);
525 
526 	fmd_hdl_debug(hdl, "serd_destroy %s", name);
527 }
528 
529 int
530 fmd_serd_exists(fmd_hdl_t *hdl, const char *name)
531 {
532 	fmd_module_t *mp = (fmd_module_t *)hdl;
533 
534 	return (fmd_serd_eng_lookup(&mp->mod_serds, name) != NULL);
535 }
536 
537 void
538 fmd_serd_reset(fmd_hdl_t *hdl, const char *name)
539 {
540 	fmd_module_t *mp = (fmd_module_t *)hdl;
541 	fmd_serd_eng_t *sgp;
542 
543 	if ((sgp = fmd_serd_eng_lookup(&mp->mod_serds, name)) == NULL) {
544 		zed_log_msg(LOG_ERR, "serd engine '%s' does not exist", name);
545 		return;
546 	}
547 
548 	fmd_serd_eng_reset(sgp);
549 
550 	fmd_hdl_debug(hdl, "serd_reset %s", name);
551 }
552 
553 int
554 fmd_serd_record(fmd_hdl_t *hdl, const char *name, fmd_event_t *ep)
555 {
556 	fmd_module_t *mp = (fmd_module_t *)hdl;
557 	fmd_serd_eng_t *sgp;
558 	int err;
559 
560 	if ((sgp = fmd_serd_eng_lookup(&mp->mod_serds, name)) == NULL) {
561 		zed_log_msg(LOG_ERR, "failed to add record to SERD engine '%s'",
562 		    name);
563 		return (0);
564 	}
565 	err = fmd_serd_eng_record(sgp, ep->ev_hrt);
566 
567 	return (err);
568 }
569 
570 /* FMD Timers */
571 
572 static void
573 _timer_notify(union sigval sv)
574 {
575 	fmd_timer_t *ftp = sv.sival_ptr;
576 	fmd_hdl_t *hdl = ftp->ft_hdl;
577 	fmd_module_t *mp = (fmd_module_t *)hdl;
578 	const fmd_hdl_ops_t *ops = mp->mod_info->fmdi_ops;
579 	struct itimerspec its;
580 
581 	fmd_hdl_debug(hdl, "timer fired (%p)", ftp->ft_tid);
582 
583 	/* disarm the timer */
584 	memset(&its, 0, sizeof (struct itimerspec));
585 	timer_settime(ftp->ft_tid, 0, &its, NULL);
586 
587 	/* Note that the fmdo_timeout can remove this timer */
588 	if (ops->fmdo_timeout != NULL)
589 		ops->fmdo_timeout(hdl, ftp, ftp->ft_arg);
590 }
591 
592 /*
593  * Install a new timer which will fire at least delta nanoseconds after the
594  * current time. After the timeout has expired, the module's fmdo_timeout
595  * entry point is called.
596  */
597 fmd_timer_t *
598 fmd_timer_install(fmd_hdl_t *hdl, void *arg, fmd_event_t *ep, hrtime_t delta)
599 {
600 	(void) ep;
601 	struct sigevent sev;
602 	struct itimerspec its;
603 	fmd_timer_t *ftp;
604 
605 	ftp = fmd_hdl_alloc(hdl, sizeof (fmd_timer_t), FMD_SLEEP);
606 	ftp->ft_arg = arg;
607 	ftp->ft_hdl = hdl;
608 
609 	its.it_value.tv_sec = delta / 1000000000;
610 	its.it_value.tv_nsec = delta % 1000000000;
611 	its.it_interval.tv_sec = its.it_value.tv_sec;
612 	its.it_interval.tv_nsec = its.it_value.tv_nsec;
613 
614 	sev.sigev_notify = SIGEV_THREAD;
615 	sev.sigev_notify_function = _timer_notify;
616 	sev.sigev_notify_attributes = NULL;
617 	sev.sigev_value.sival_ptr = ftp;
618 
619 	timer_create(CLOCK_REALTIME, &sev, &ftp->ft_tid);
620 	timer_settime(ftp->ft_tid, 0, &its, NULL);
621 
622 	fmd_hdl_debug(hdl, "installing timer for %d secs (%p)",
623 	    (int)its.it_value.tv_sec, ftp->ft_tid);
624 
625 	return (ftp);
626 }
627 
628 void
629 fmd_timer_remove(fmd_hdl_t *hdl, fmd_timer_t *ftp)
630 {
631 	fmd_hdl_debug(hdl, "removing timer (%p)", ftp->ft_tid);
632 
633 	timer_delete(ftp->ft_tid);
634 
635 	fmd_hdl_free(hdl, ftp, sizeof (fmd_timer_t));
636 }
637 
638 /* Name-Value Pair Lists */
639 
640 nvlist_t *
641 fmd_nvl_create_fault(fmd_hdl_t *hdl, const char *class, uint8_t certainty,
642     nvlist_t *asru, nvlist_t *fru, nvlist_t *resource)
643 {
644 	(void) hdl;
645 	nvlist_t *nvl;
646 	int err = 0;
647 
648 	if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0)
649 		zed_log_die("failed to xalloc fault nvlist");
650 
651 	err |= nvlist_add_uint8(nvl, FM_VERSION, FM_FAULT_VERSION);
652 	err |= nvlist_add_string(nvl, FM_CLASS, class);
653 	err |= nvlist_add_uint8(nvl, FM_FAULT_CERTAINTY, certainty);
654 
655 	if (asru != NULL)
656 		err |= nvlist_add_nvlist(nvl, FM_FAULT_ASRU, asru);
657 	if (fru != NULL)
658 		err |= nvlist_add_nvlist(nvl, FM_FAULT_FRU, fru);
659 	if (resource != NULL)
660 		err |= nvlist_add_nvlist(nvl, FM_FAULT_RESOURCE, resource);
661 
662 	if (err)
663 		zed_log_die("failed to populate nvlist: %s\n", strerror(err));
664 
665 	return (nvl);
666 }
667 
668 /*
669  * sourced from fmd_string.c
670  */
671 static int
672 fmd_strmatch(const char *s, const char *p)
673 {
674 	char c;
675 
676 	if (p == NULL)
677 		return (0);
678 
679 	if (s == NULL)
680 		s = ""; /* treat NULL string as the empty string */
681 
682 	do {
683 		if ((c = *p++) == '\0')
684 			return (*s == '\0');
685 
686 		if (c == '*') {
687 			while (*p == '*')
688 				p++; /* consecutive *'s can be collapsed */
689 
690 			if (*p == '\0')
691 				return (1);
692 
693 			while (*s != '\0') {
694 				if (fmd_strmatch(s++, p) != 0)
695 					return (1);
696 			}
697 
698 			return (0);
699 		}
700 	} while (c == *s++);
701 
702 	return (0);
703 }
704 
705 int
706 fmd_nvl_class_match(fmd_hdl_t *hdl, nvlist_t *nvl, const char *pattern)
707 {
708 	(void) hdl;
709 	char *class;
710 
711 	return (nvl != NULL &&
712 	    nvlist_lookup_string(nvl, FM_CLASS, &class) == 0 &&
713 	    fmd_strmatch(class, pattern));
714 }
715 
716 nvlist_t *
717 fmd_nvl_alloc(fmd_hdl_t *hdl, int flags)
718 {
719 	(void) hdl, (void) flags;
720 	nvlist_t *nvl = NULL;
721 
722 	if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0)
723 		return (NULL);
724 
725 	return (nvl);
726 }
727 
728 
729 /*
730  * ZED Agent specific APIs
731  */
732 
733 fmd_hdl_t *
734 fmd_module_hdl(const char *name)
735 {
736 	if (strcmp(name, "zfs-retire") == 0)
737 		return ((fmd_hdl_t *)&zfs_retire_module);
738 	if (strcmp(name, "zfs-diagnosis") == 0)
739 		return ((fmd_hdl_t *)&zfs_diagnosis_module);
740 
741 	return (NULL);
742 }
743 
744 boolean_t
745 fmd_module_initialized(fmd_hdl_t *hdl)
746 {
747 	fmd_module_t *mp = (fmd_module_t *)hdl;
748 
749 	return (mp->mod_info != NULL);
750 }
751 
752 /*
753  * fmd_module_recv is called for each event that is received by
754  * the fault manager that has a class that matches one of the
755  * module's subscriptions.
756  */
757 void
758 fmd_module_recv(fmd_hdl_t *hdl, nvlist_t *nvl, const char *class)
759 {
760 	fmd_module_t *mp = (fmd_module_t *)hdl;
761 	const fmd_hdl_ops_t *ops = mp->mod_info->fmdi_ops;
762 	fmd_event_t faux_event = {0};
763 	int64_t *tv;
764 	uint_t n;
765 
766 	/*
767 	 * Will need to normalized this if we persistently store the case data
768 	 */
769 	if (nvlist_lookup_int64_array(nvl, FM_EREPORT_TIME, &tv, &n) == 0)
770 		faux_event.ev_hrt = tv[0] * NANOSEC + tv[1];
771 	else
772 		faux_event.ev_hrt = 0;
773 
774 	ops->fmdo_recv(hdl, &faux_event, nvl, class);
775 
776 	mp->mod_stats.ms_accepted.fmds_value.ui64++;
777 
778 	/* TBD - should we initiate fm_module_gc() periodically? */
779 }
780