1184cd04cScth /*
2184cd04cScth  * CDDL HEADER START
3184cd04cScth  *
4184cd04cScth  * The contents of this file are subject to the terms of the
5184cd04cScth  * Common Development and Distribution License (the "License").
6184cd04cScth  * You may not use this file except in compliance with the License.
7184cd04cScth  *
8184cd04cScth  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9184cd04cScth  * or http://www.opensolaris.org/os/licensing.
10184cd04cScth  * See the License for the specific language governing permissions
11184cd04cScth  * and limitations under the License.
12184cd04cScth  *
13184cd04cScth  * When distributing Covered Code, include this CDDL HEADER in each
14184cd04cScth  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15184cd04cScth  * If applicable, add the following below this CDDL HEADER, with the
16184cd04cScth  * fields enclosed by brackets "[]" replaced with your own identifying
17184cd04cScth  * information: Portions Copyright [yyyy] [name of copyright owner]
18184cd04cScth  *
19184cd04cScth  * CDDL HEADER END
20184cd04cScth  */
21184cd04cScth 
22184cd04cScth /*
23cbf75e67SStephen Hanson  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24184cd04cScth  * Use is subject to license terms.
25*0244979bSAlek Pinchuk  * Copyright 2016 Nexenta Systems, Inc.  All rights reserved.
26184cd04cScth  */
27184cd04cScth 
28184cd04cScth /*
29184cd04cScth  * Disk Monitor
30184cd04cScth  */
31184cd04cScth #include <sys/types.h>
32184cd04cScth #include <sys/stat.h>
33184cd04cScth #include <fcntl.h>
34184cd04cScth #include <time.h>
35184cd04cScth #include <stdio.h>
36184cd04cScth #include <stdlib.h>
37184cd04cScth #include <strings.h>
38184cd04cScth #include <stdarg.h>
39184cd04cScth #include <errno.h>
40184cd04cScth #include <signal.h>
41184cd04cScth #include <unistd.h>
42184cd04cScth #include <pthread.h>
43184cd04cScth #include <libnvpair.h>
44184cd04cScth #include <fm/fmd_api.h>
45184cd04cScth #include <fm/fmd_fmri.h>
46184cd04cScth #include <sys/fm/protocol.h>
47184cd04cScth #include <sys/fm/io/disk.h>
48184cd04cScth #include <fm/libtopo.h>
49184cd04cScth 
50184cd04cScth #include "disk_monitor.h"
51184cd04cScth #include "hotplug_mgr.h"
52184cd04cScth #include "schg_mgr.h"
53184cd04cScth #include "topo_gather.h"
54184cd04cScth #include "dm_platform.h"
55184cd04cScth 
56184cd04cScth #define	THIS_FMD_MODULE_NAME "disk-monitor"
57184cd04cScth 
58184cd04cScth static enum disk_init_state {
59184cd04cScth 	INIT_STATE_NONE = 0,
60184cd04cScth 	STATE_CHANGE_MGR_INITTED = 2,
61184cd04cScth 	HOTPLUG_MGR_INITTED = 4
62184cd04cScth } g_init_state = INIT_STATE_NONE;
63184cd04cScth 
64184cd04cScth typedef enum {
65184cd04cScth 	LT_SUSPECT,
66184cd04cScth 	LT_REPAIRED
67184cd04cScth } fm_list_type_t;
68184cd04cScth 
69184cd04cScth /*
70184cd04cScth  * Global verbosity flag -- controls chattiness of debug messages and
71184cd04cScth  * warnings.  Its value is determined by the fmd property "log-level"
72184cd04cScth  * settable in the DE's .conf file.
73184cd04cScth  */
74184cd04cScth log_class_t			g_verbose = 0;
75184cd04cScth cfgdata_t			*config_data = NULL;
76184cd04cScth fmd_hdl_t			*g_fm_hdl = NULL;
77184cd04cScth 
78184cd04cScth static const fmd_prop_t		fmd_props[];
79184cd04cScth 
80184cd04cScth static void
diskmon_teardown_all(void)81184cd04cScth diskmon_teardown_all(void)
82184cd04cScth {
83184cd04cScth 	cleanup_hotplug_manager();
84184cd04cScth 	cleanup_state_change_manager(config_data);
85184cd04cScth 	config_fini();
86184cd04cScth }
87184cd04cScth 
88184cd04cScth static int
count_disks(diskmon_t * disklistp)89184cd04cScth count_disks(diskmon_t *disklistp)
90184cd04cScth {
91184cd04cScth 	int i = 0;
92184cd04cScth 
93184cd04cScth 	while (disklistp != NULL) {
94184cd04cScth 		i++;
95184cd04cScth 		disklistp = disklistp->next;
96184cd04cScth 	}
97184cd04cScth 
98184cd04cScth 	return (i);
99184cd04cScth }
100184cd04cScth 
101184cd04cScth static int
diskmon_init(void)102184cd04cScth diskmon_init(void)
103184cd04cScth {
104184cd04cScth 	/*
105184cd04cScth 	 * Block the generation of state change events (generated by the
106184cd04cScth 	 * hotplug manager thread) here; they will be unblocked after the
107184cd04cScth 	 * state change manager thread is ready to accept state changes
108184cd04cScth 	 * (shortly after it starts).
109184cd04cScth 	 */
110184cd04cScth 	block_state_change_events();
111184cd04cScth 
112184cd04cScth 	if (dm_platform_init() != 0)
113184cd04cScth 		goto cleanup;
114184cd04cScth 
115184cd04cScth 	if (init_hotplug_manager() != 0)
116184cd04cScth 		goto cleanup;
117184cd04cScth 	else
118184cd04cScth 		g_init_state |= HOTPLUG_MGR_INITTED;
119184cd04cScth 
120184cd04cScth 	if (init_state_change_manager(config_data) != 0)
121184cd04cScth 		goto cleanup;
122184cd04cScth 	else
123184cd04cScth 		g_init_state |= STATE_CHANGE_MGR_INITTED;
124184cd04cScth 
125184cd04cScth 	return (E_SUCCESS);
126184cd04cScth 
127184cd04cScth cleanup:
128184cd04cScth 
129184cd04cScth 	unblock_state_change_events();
130184cd04cScth 
131184cd04cScth 	/*
132184cd04cScth 	 * The cleanup order here does matter, due to dependencies between the
133184cd04cScth 	 * managers.
134184cd04cScth 	 */
135184cd04cScth 	if (g_init_state & HOTPLUG_MGR_INITTED)
136184cd04cScth 		cleanup_hotplug_manager();
137184cd04cScth 	if (g_init_state & STATE_CHANGE_MGR_INITTED)
138184cd04cScth 		cleanup_state_change_manager(config_data);
139184cd04cScth 	dm_platform_fini();
140184cd04cScth 
141184cd04cScth 	return (E_ERROR);
142184cd04cScth }
143184cd04cScth 
144184cd04cScth static void
dm_fault_execute_actions(fmd_hdl_t * hdl,diskmon_t * diskp,nvlist_t * nvl)145184cd04cScth dm_fault_execute_actions(fmd_hdl_t *hdl, diskmon_t *diskp, nvlist_t *nvl)
146184cd04cScth {
147184cd04cScth 	const char		*action_prop = NULL;
148184cd04cScth 	const char		*action_string;
149184cd04cScth 
150184cd04cScth 	/*
151184cd04cScth 	 * The predictive failure action is the activation of the fault
152184cd04cScth 	 * indicator.
153184cd04cScth 	 */
154184cd04cScth 	if (fmd_nvl_class_match(hdl, nvl,
155184cd04cScth 	    DISK_ERROR_CLASS "." FM_FAULT_DISK_OVERTEMP))
156184cd04cScth 		action_prop = DISK_PROP_OTEMPACTION;
157184cd04cScth 
158184cd04cScth 	if (fmd_nvl_class_match(hdl, nvl,
159184cd04cScth 	    DISK_ERROR_CLASS "." FM_FAULT_DISK_TESTFAIL))
160184cd04cScth 		action_prop = DISK_PROP_STFAILACTION;
161184cd04cScth 
162*0244979bSAlek Pinchuk 	if (fmd_nvl_class_match(hdl, nvl,
163*0244979bSAlek Pinchuk 	    DISK_ERROR_CLASS "." FM_FAULT_SSM_WEAROUT))
164*0244979bSAlek Pinchuk 		action_prop = DISK_PROP_SSMWEAROUTACTION;
165*0244979bSAlek Pinchuk 
166184cd04cScth 	dm_fault_indicator_set(diskp, INDICATOR_ON);
167184cd04cScth 
168184cd04cScth 	if (action_prop != NULL &&
169184cd04cScth 	    (action_string = dm_prop_lookup(diskp->props, action_prop))
170184cd04cScth 	    != NULL) {
171184cd04cScth 
172184cd04cScth 		if (dm_platform_indicator_execute(action_string) != 0) {
173184cd04cScth 			log_warn("Fault action `%s' did not successfully "
174184cd04cScth 			    "complete.\n", action_string);
175184cd04cScth 		}
176184cd04cScth 	}
177184cd04cScth }
178184cd04cScth 
179184cd04cScth static void
diskmon_agent_repair(fmd_hdl_t * hdl,nvlist_t * nvl,int repair)18025c6ff4bSstephh diskmon_agent_repair(fmd_hdl_t *hdl, nvlist_t *nvl, int repair)
181184cd04cScth {
182184cd04cScth 	char		*uuid = NULL;
183184cd04cScth 	nvlist_t	**nva;
184184cd04cScth 	uint_t		nvc;
185184cd04cScth 	diskmon_t	*diskp;
186184cd04cScth 	nvlist_t	*fmri;
187184cd04cScth 	nvlist_t	*fltnvl;
188184cd04cScth 	int		err = 0;
189184cd04cScth 
190184cd04cScth 	err |= nvlist_lookup_string(nvl, FM_SUSPECT_UUID, &uuid);
191184cd04cScth 	err |= nvlist_lookup_nvlist_array(nvl, FM_SUSPECT_FAULT_LIST,
192184cd04cScth 	    &nva, &nvc);
193184cd04cScth 	if (err != 0)
194184cd04cScth 		return;
195184cd04cScth 
196184cd04cScth 	while (nvc-- != 0) {
197184cd04cScth 
198184cd04cScth 		fltnvl = *nva++;
199184cd04cScth 
200184cd04cScth 		if (nvlist_lookup_nvlist(fltnvl, FM_FAULT_RESOURCE, &fmri)
201184cd04cScth 		    != 0)
202184cd04cScth 			continue;
203184cd04cScth 
204184cd04cScth 		if ((diskp = dm_fmri_to_diskmon(hdl, fmri)) == NULL)
205184cd04cScth 			continue;
206184cd04cScth 
207184cd04cScth 		log_msg(MM_MAIN, "Disk %s repaired!\n",
208184cd04cScth 		    diskp->location);
209184cd04cScth 
210184cd04cScth 		dm_fault_indicator_set(diskp, INDICATOR_OFF);
211184cd04cScth 
212184cd04cScth 		dm_state_change(diskp, HPS_REPAIRED);
213184cd04cScth 	}
214184cd04cScth 
21525c6ff4bSstephh 	if (repair)
21625c6ff4bSstephh 		fmd_case_uuresolved(hdl, uuid);
21725c6ff4bSstephh 
218184cd04cScth }
219184cd04cScth 
220184cd04cScth static void
diskmon_agent_suspect(fmd_hdl_t * hdl,nvlist_t * nvl)221184cd04cScth diskmon_agent_suspect(fmd_hdl_t *hdl, nvlist_t *nvl)
222184cd04cScth {
223184cd04cScth 	char		*uuid = NULL;
224184cd04cScth 	nvlist_t	**nva;
225184cd04cScth 	uint_t		nvc;
226184cd04cScth 	diskmon_t	*diskp;
227184cd04cScth 	nvlist_t	*fmri;
228184cd04cScth 	nvlist_t	*fltnvl;
229184cd04cScth 	int		err = 0;
230184cd04cScth 
231184cd04cScth 	err |= nvlist_lookup_string(nvl, FM_SUSPECT_UUID, &uuid);
232184cd04cScth 	err |= nvlist_lookup_nvlist_array(nvl, FM_SUSPECT_FAULT_LIST,
233184cd04cScth 	    &nva, &nvc);
234184cd04cScth 	if (err != 0)
235184cd04cScth 		return;
236184cd04cScth 
237184cd04cScth 	while (nvc-- != 0 && !fmd_case_uuclosed(hdl, uuid)) {
238184cd04cScth 
239184cd04cScth 		fltnvl = *nva++;
240184cd04cScth 
241184cd04cScth 		if (nvlist_lookup_nvlist(fltnvl, FM_FAULT_RESOURCE, &fmri) != 0)
242184cd04cScth 			continue;
243184cd04cScth 
244184cd04cScth 		if ((diskp = dm_fmri_to_diskmon(hdl, fmri)) == NULL)
245184cd04cScth 			continue;
246184cd04cScth 
247184cd04cScth 		/* Execute the actions associated with this fault */
248184cd04cScth 		dm_fault_execute_actions(hdl, diskp,  fltnvl);
249184cd04cScth 
250184cd04cScth 		/*
251184cd04cScth 		 * Send a state change event to the state change manager
252184cd04cScth 		 */
253184cd04cScth 		dm_state_change(diskp, HPS_FAULTED);
254184cd04cScth 	}
255184cd04cScth 
256184cd04cScth 	if (!fmd_case_uuclosed(hdl, uuid)) {
257184cd04cScth 		/* Case is closed */
258184cd04cScth 		fmd_case_uuclose(hdl, uuid);
259184cd04cScth 	}
260184cd04cScth }
261184cd04cScth 
262184cd04cScth /*ARGSUSED*/
263184cd04cScth static void
diskmon_recv(fmd_hdl_t * hdl,fmd_event_t * ep,nvlist_t * nvl,const char * class)264184cd04cScth diskmon_recv(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl, const char *class)
265184cd04cScth {
266184cd04cScth 	diskmon_t	*diskp;
267184cd04cScth 	nvlist_t	*fmri;
268184cd04cScth 
269184cd04cScth 	if (g_verbose & MM_MAIN)
270184cd04cScth 		nvlist_print(stderr, nvl);
271184cd04cScth 
272184cd04cScth 	/*
273184cd04cScth 	 * Act on the fault suspect list or repaired list (embedded agent
274184cd04cScth 	 * action).
275184cd04cScth 	 */
27625c6ff4bSstephh 	if (fmd_nvl_class_match(hdl, nvl, FM_LIST_REPAIRED_CLASS)) {
277184cd04cScth 
27825c6ff4bSstephh 		diskmon_agent_repair(hdl, nvl, 1);
279184cd04cScth 		return;
280184cd04cScth 
28125c6ff4bSstephh 	} else if (fmd_nvl_class_match(hdl, nvl, FM_LIST_UPDATED_CLASS)) {
28225c6ff4bSstephh 
28325c6ff4bSstephh 		diskmon_agent_repair(hdl, nvl, 0);
28425c6ff4bSstephh 		return;
28525c6ff4bSstephh 
28625c6ff4bSstephh 	} else if (fmd_nvl_class_match(hdl, nvl, FM_LIST_SUSPECT_CLASS)) {
287184cd04cScth 
288184cd04cScth 		diskmon_agent_suspect(hdl, nvl);
289184cd04cScth 		return;
290cbf75e67SStephen Hanson 	} else if (fmd_nvl_class_match(hdl, nvl, FM_LIST_RESOLVED_CLASS)) {
291cbf75e67SStephen Hanson 		return;
292184cd04cScth 	}
293184cd04cScth 
294184cd04cScth 	/*
295184cd04cScth 	 * If we get any replayed faults, set the diskmon's faulted
296184cd04cScth 	 * flag for the appropriate fault, then change the diskmon's state
297184cd04cScth 	 * to faulted.
298184cd04cScth 	 */
299184cd04cScth 	if (fmd_nvl_class_match(hdl, nvl, DISK_ERROR_CLASS ".*")) {
300184cd04cScth 
301184cd04cScth 		if (nvlist_lookup_nvlist(nvl, FM_FAULT_RESOURCE,
302184cd04cScth 		    &fmri) != 0)
303184cd04cScth 			return;
304184cd04cScth 
305184cd04cScth 		if ((diskp = dm_fmri_to_diskmon(hdl, fmri)) == NULL)
306184cd04cScth 			return;
307184cd04cScth 
308184cd04cScth 		/* Execute the actions associated with this fault */
309184cd04cScth 		dm_fault_execute_actions(hdl, diskp, nvl);
310184cd04cScth 
311184cd04cScth 		/*
312184cd04cScth 		 * If the fault wasn't generated by this module, send a
313184cd04cScth 		 * state change event to the state change manager
314184cd04cScth 		 */
315184cd04cScth 		dm_state_change(diskp, HPS_FAULTED);
316184cd04cScth 		return;
317184cd04cScth 	}
318184cd04cScth }
319184cd04cScth 
320184cd04cScth static const fmd_hdl_ops_t fmd_ops = {
321184cd04cScth 	diskmon_recv,	/* fmdo_recv */
322184cd04cScth 	NULL,		/* fmdo_timeout */
323184cd04cScth 	NULL,		/* fmdo_close */
324184cd04cScth 	NULL,		/* fmdo_stats */
325184cd04cScth 	NULL,		/* fmdo_gc */
326184cd04cScth };
327184cd04cScth 
328184cd04cScth static const fmd_prop_t fmd_props[] = {
329184cd04cScth 	{ GLOBAL_PROP_LOG_LEVEL, FMD_TYPE_UINT32, "0" },
330184cd04cScth 	{ NULL, 0, NULL }
331184cd04cScth };
332184cd04cScth 
333184cd04cScth static const fmd_hdl_info_t fmd_info = {
334184cd04cScth 	"Disk Monitor",
335184cd04cScth 	DISK_MONITOR_MODULE_VERSION,
336184cd04cScth 	&fmd_ops,
337184cd04cScth 	fmd_props
338184cd04cScth };
339184cd04cScth 
340184cd04cScth void
_fmd_init(fmd_hdl_t * hdl)341184cd04cScth _fmd_init(fmd_hdl_t *hdl)
342184cd04cScth {
343184cd04cScth 	fmd_case_t	*cp;
344184cd04cScth 	int		disk_count;
345184cd04cScth 
346184cd04cScth 	g_fm_hdl = hdl;
347184cd04cScth 
348184cd04cScth 	if (fmd_hdl_register(hdl, FMD_API_VERSION, &fmd_info) != 0) {
349184cd04cScth 		return;
350184cd04cScth 	}
351184cd04cScth 
352184cd04cScth 	if (config_init()) {
353184cd04cScth 		log_err("Could not initialize configuration!\n");
354184cd04cScth 		fmd_hdl_unregister(hdl);
355184cd04cScth 		return;
356184cd04cScth 	}
357184cd04cScth 
358184cd04cScth 	if (config_get(hdl, fmd_props)) {
359184cd04cScth 		config_fini();
360184cd04cScth 		log_err("Could not retrieve configuration from libtopo!\n");
361184cd04cScth 		fmd_hdl_unregister(hdl);
362184cd04cScth 		return;
363184cd04cScth 	}
364184cd04cScth 
365184cd04cScth 	/*
366184cd04cScth 	 * If there are no disks to monitor, bail out
367184cd04cScth 	 */
368184cd04cScth 	if ((disk_count = count_disks(config_data->disk_list)) == 0) {
369184cd04cScth 		config_fini();
370184cd04cScth 		fmd_hdl_unregister(hdl);
371184cd04cScth 		return;
372184cd04cScth 	}
373184cd04cScth 
374184cd04cScth 	if (diskmon_init() == E_ERROR) {
375184cd04cScth 		config_fini();
376184cd04cScth 		fmd_hdl_unregister(hdl);
377184cd04cScth 		return;
378184cd04cScth 	}
379184cd04cScth 
380184cd04cScth 	log_msg(MM_MAIN, "Monitoring %d disks.\n", disk_count);
381184cd04cScth 
382184cd04cScth 	/*
383184cd04cScth 	 * Iterate over all active cases.
384184cd04cScth 	 * Since we automatically solve all cases, these cases must have
385184cd04cScth 	 * had the fault added, but the DE must have been interrupted
386184cd04cScth 	 * before they were solved.
387184cd04cScth 	 */
388184cd04cScth 	for (cp = fmd_case_next(hdl, NULL);
389184cd04cScth 	    cp != NULL; cp = fmd_case_next(hdl, cp)) {
390184cd04cScth 
391184cd04cScth 		if (!fmd_case_solved(hdl, cp))
392184cd04cScth 			fmd_case_solve(hdl, cp);
393184cd04cScth 	}
394184cd04cScth }
395184cd04cScth 
396184cd04cScth /*ARGSUSED*/
397184cd04cScth void
_fmd_fini(fmd_hdl_t * hdl)398184cd04cScth _fmd_fini(fmd_hdl_t *hdl)
399184cd04cScth {
400184cd04cScth 	diskmon_teardown_all();
401184cd04cScth 	g_fm_hdl = NULL;
402184cd04cScth }
403