1#!/bin/sh
2#
3# Turn off/on the VDEV's enclosure fault LEDs when the pool's state changes.
4#
5# Turn the VDEV's fault LED on if it becomes FAULTED, DEGRADED or UNAVAIL.
6# Turn the 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 [ ! -e "$file" ] ; then
63		return 3
64	fi
65
66	# If another process is accessing the LED when we attempt to update it,
67	# the update will be lost so retry until the LED actually changes or we
68	# timeout.
69	for _ in $(seq 1 5); do
70		# We want to check the current state first, since writing to the
71		# 'fault' entry always causes a SES command, even if the
72		# current state is already what you want.
73		current=$(cat "${file}")
74
75		# On some enclosures if you write 1 to fault, and read it back,
76		# it will return 2.  Treat all non-zero values as 1 for
77		# simplicity.
78		if [ "$current" != "0" ] ; then
79			current=1
80		fi
81
82		if [ "$current" != "$val" ] ; then
83			echo "$val" > "$file"
84			zed_log_msg "vdev $vdev set '$file' LED to $val"
85		else
86			break
87		fi
88        done
89}
90
91state_to_val()
92{
93	state="$1"
94	if [ "$state" = "FAULTED" ] || [ "$state" = "DEGRADED" ] || \
95	   [ "$state" = "UNAVAIL" ] ; then
96		echo 1
97	elif [ "$state" = "ONLINE" ] ; then
98		echo 0
99	fi
100}
101
102# process_pool ([pool])
103#
104# Iterate through a pool (or pools) and set the VDEV's enclosure slot LEDs to
105# the VDEV's state.
106#
107# Arguments
108#   pool:	Optional pool name.  If not specified, iterate though all pools.
109#
110# Return
111#  0 on success, 3 on missing sysfs path
112#
113process_pool()
114{
115	pool="$1"
116	rc=0
117
118	# Lookup all the current LED values and paths in parallel
119	#shellcheck disable=SC2016
120	cmd='echo led_token=$(cat "$VDEV_ENC_SYSFS_PATH/fault"),"$VDEV_ENC_SYSFS_PATH",'
121	out=$($ZPOOL status -vc "$cmd" "$pool" | grep 'led_token=')
122
123	#shellcheck disable=SC2034
124	echo "$out" | while read -r vdev state read write chksum therest; do
125		# Read out current LED value and path
126		tmp=$(echo "$therest" | sed 's/^.*led_token=//g')
127		vdev_enc_sysfs_path=$(echo "$tmp" | awk -F ',' '{print $2}')
128		current_val=$(echo "$tmp" | awk -F ',' '{print $1}')
129
130		if [ "$current_val" != "0" ] ; then
131			current_val=1
132		fi
133
134		if [ -z "$vdev_enc_sysfs_path" ] ; then
135			# Skip anything with no sysfs LED entries
136			continue
137		fi
138
139		if [ ! -e "$vdev_enc_sysfs_path/fault" ] ; then
140			#shellcheck disable=SC2030
141			rc=1
142			zed_log_msg "vdev $vdev '$file/fault' doesn't exist"
143			continue;
144		fi
145
146		val=$(state_to_val "$state")
147
148		if [ "$current_val" = "$val" ] ; then
149			# LED is already set correctly
150			continue;
151		fi
152
153		if ! check_and_set_led "$vdev_enc_sysfs_path/fault" "$val"; then
154			rc=1
155		fi
156
157	done
158
159	#shellcheck disable=SC2031
160	if [ "$rc" = "0" ] ; then
161		return 0
162	else
163		# We didn't see a sysfs entry that we wanted to set
164		return 3
165	fi
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