1#!/bin/sh
2#
3# Turn off/on vdevs' enclosure fault LEDs when their pool's state changes.
4#
5# Turn a vdev's fault LED on if it becomes FAULTED, DEGRADED or UNAVAIL.
6# Turn its LED off when it's back ONLINE again.
7#
8# This script run in two basic modes:
9#
10# 1. If $ZEVENT_VDEV_ENC_SYSFS_PATH and $ZEVENT_VDEV_STATE_STR are set, then
11# only set the LED for that particular vdev. This is the case for statechange
12# events and some vdev_* events.
13#
14# 2. If those vars are not set, then check the state of all vdevs in the pool
15# and set the LEDs accordingly.  This is the case for pool_import events.
16#
17# Note that this script requires that your enclosure be supported by the
18# Linux SCSI Enclosure services (SES) driver.  The script will do nothing
19# if you have no enclosure, or if your enclosure isn't supported.
20#
21# Exit codes:
22#   0: enclosure led successfully set
23#   1: enclosure leds not available
24#   2: enclosure leds administratively disabled
25#   3: The led sysfs path passed from ZFS does not exist
26#   4: $ZPOOL not set
27#   5: awk is not installed
28
29[ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc"
30. "${ZED_ZEDLET_DIR}/zed-functions.sh"
31
32if [ ! -d /sys/class/enclosure ] ; then
33	exit 1
34fi
35
36if [ "${ZED_USE_ENCLOSURE_LEDS}" != "1" ] ; then
37	exit 2
38fi
39
40zed_check_cmd "$ZPOOL" || exit 4
41zed_check_cmd awk || exit 5
42
43# Global used in set_led debug print
44vdev=""
45
46# check_and_set_led (file, val)
47#
48# Read an enclosure sysfs file, and write it if it's not already set to 'val'
49#
50# Arguments
51#   file: sysfs file to set (like /sys/class/enclosure/0:0:1:0/SLOT 10/fault)
52#   val: value to set it to
53#
54# Return
55#  0 on success, 3 on missing sysfs path
56#
57check_and_set_led()
58{
59	file="$1"
60	val="$2"
61
62	if [ -z "$val" ]; then
63		return 0
64	fi
65
66	if [ ! -e "$file" ] ; then
67		return 3
68	fi
69
70	# If another process is accessing the LED when we attempt to update it,
71	# the update will be lost so retry until the LED actually changes or we
72	# timeout.
73	for _ in 1 2 3 4 5; do
74		# We want to check the current state first, since writing to the
75		# 'fault' entry always causes a SES command, even if the
76		# current state is already what you want.
77		read -r current < "${file}"
78
79		# On some enclosures if you write 1 to fault, and read it back,
80		# it will return 2.  Treat all non-zero values as 1 for
81		# simplicity.
82		if [ "$current" != "0" ] ; then
83			current=1
84		fi
85
86		if [ "$current" != "$val" ] ; then
87			echo "$val" > "$file"
88			zed_log_msg "vdev $vdev set '$file' LED to $val"
89		else
90			break
91		fi
92	done
93}
94
95state_to_val()
96{
97	state="$1"
98	case "$state" in
99		FAULTED|DEGRADED|UNAVAIL)
100			echo 1
101			;;
102		ONLINE)
103			echo 0
104			;;
105	esac
106}
107
108# process_pool (pool)
109#
110# Iterate through a pool and set the vdevs' enclosure slot LEDs to
111# those vdevs' state.
112#
113# Arguments
114#   pool:	Pool name.
115#
116# Return
117#  0 on success, 3 on missing sysfs path
118#
119process_pool()
120{
121	pool="$1"
122
123	# The output will be the vdevs only (from "grep '/dev/'"):
124	#
125	#    U45     ONLINE       0     0     0   /dev/sdk          0
126	#    U46     ONLINE       0     0     0   /dev/sdm          0
127	#    U47     ONLINE       0     0     0   /dev/sdn          0
128	#    U50     ONLINE       0     0     0  /dev/sdbn          0
129	#
130	ZPOOL_SCRIPTS_AS_ROOT=1 $ZPOOL status -c upath,fault_led "$pool" | grep '/dev/' | (
131	rc=0
132	while read -r vdev state _ _ _ therest; do
133		# Read out current LED value and path
134		# Get dev name (like 'sda')
135		dev=$(basename "$(echo "$therest" | awk '{print $(NF-1)}')")
136		vdev_enc_sysfs_path=$(realpath "/sys/class/block/$dev/device/enclosure_device"*)
137		current_val=$(echo "$therest" | awk '{print $NF}')
138
139		if [ "$current_val" != "0" ] ; then
140			current_val=1
141		fi
142
143		if [ -z "$vdev_enc_sysfs_path" ] ; then
144			# Skip anything with no sysfs LED entries
145			continue
146		fi
147
148		if [ ! -e "$vdev_enc_sysfs_path/fault" ] ; then
149			rc=3
150			zed_log_msg "vdev $vdev '$file/fault' doesn't exist"
151			continue
152		fi
153
154		val=$(state_to_val "$state")
155
156		if [ "$current_val" = "$val" ] ; then
157			# LED is already set correctly
158			continue
159		fi
160
161		if ! check_and_set_led "$vdev_enc_sysfs_path/fault" "$val"; then
162			rc=3
163		fi
164	done
165	exit "$rc"; )
166}
167
168if [ -n "$ZEVENT_VDEV_ENC_SYSFS_PATH" ] && [ -n "$ZEVENT_VDEV_STATE_STR" ] ; then
169	# Got a statechange for an individual vdev
170	val=$(state_to_val "$ZEVENT_VDEV_STATE_STR")
171	vdev=$(basename "$ZEVENT_VDEV_PATH")
172	check_and_set_led "$ZEVENT_VDEV_ENC_SYSFS_PATH/fault" "$val"
173else
174	# Process the entire pool
175	poolname=$(zed_guid_to_pool "$ZEVENT_POOL_GUID")
176	process_pool "$poolname"
177fi
178