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