xref: /freebsd/sys/contrib/openzfs/cmd/zed/zed.d/statechange-sync-led.sh (revision dd32d6b29d49838c99d38ba30846ade210b2e6f7)
1*dd32d6b2SMartin Matuska#!/bin/sh
2*dd32d6b2SMartin Matuska# shellcheck disable=SC2154
3*dd32d6b2SMartin Matuska#
4*dd32d6b2SMartin Matuska# Turn off/on vdevs' enclosure fault LEDs when their pool's state changes.
5*dd32d6b2SMartin Matuska#
6*dd32d6b2SMartin Matuska# Turn a vdev's fault LED on if it becomes FAULTED, DEGRADED or UNAVAIL.
7*dd32d6b2SMartin Matuska# Turn its LED off when it's back ONLINE again.
8*dd32d6b2SMartin Matuska#
9*dd32d6b2SMartin Matuska# This script run in two basic modes:
10*dd32d6b2SMartin Matuska#
11*dd32d6b2SMartin Matuska# 1. If $ZEVENT_VDEV_ENC_SYSFS_PATH and $ZEVENT_VDEV_STATE_STR are set, then
12*dd32d6b2SMartin Matuska# only set the LED for that particular vdev. This is the case for statechange
13*dd32d6b2SMartin Matuska# events and some vdev_* events.
14*dd32d6b2SMartin Matuska#
15*dd32d6b2SMartin Matuska# 2. If those vars are not set, then check the state of all vdevs in the pool
16*dd32d6b2SMartin Matuska# and set the LEDs accordingly.  This is the case for pool_import events.
17*dd32d6b2SMartin Matuska#
18*dd32d6b2SMartin Matuska# Note that this script requires that your enclosure be supported by the
19*dd32d6b2SMartin Matuska# Linux SCSI Enclosure services (SES) driver.  The script will do nothing
20*dd32d6b2SMartin Matuska# if you have no enclosure, or if your enclosure isn't supported.
21*dd32d6b2SMartin Matuska#
22*dd32d6b2SMartin Matuska# Exit codes:
23*dd32d6b2SMartin Matuska#   0: enclosure led successfully set
24*dd32d6b2SMartin Matuska#   1: enclosure leds not available
25*dd32d6b2SMartin Matuska#   2: enclosure leds administratively disabled
26*dd32d6b2SMartin Matuska#   3: The led sysfs path passed from ZFS does not exist
27*dd32d6b2SMartin Matuska#   4: $ZPOOL not set
28*dd32d6b2SMartin Matuska#   5: awk is not installed
29*dd32d6b2SMartin Matuska
30*dd32d6b2SMartin Matuska[ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc"
31*dd32d6b2SMartin Matuska. "${ZED_ZEDLET_DIR}/zed-functions.sh"
32*dd32d6b2SMartin Matuska
33*dd32d6b2SMartin Matuskaif [ ! -d /sys/class/enclosure ] && [ ! -d /sys/bus/pci/slots ] ; then
34*dd32d6b2SMartin Matuska	# No JBOD enclosure or NVMe slots
35*dd32d6b2SMartin Matuska	exit 1
36*dd32d6b2SMartin Matuskafi
37*dd32d6b2SMartin Matuska
38*dd32d6b2SMartin Matuskaif [ "${ZED_USE_ENCLOSURE_LEDS}" != "1" ] ; then
39*dd32d6b2SMartin Matuska	exit 2
40*dd32d6b2SMartin Matuskafi
41*dd32d6b2SMartin Matuska
42*dd32d6b2SMartin Matuskazed_check_cmd "$ZPOOL" || exit 4
43*dd32d6b2SMartin Matuskazed_check_cmd awk || exit 5
44*dd32d6b2SMartin Matuska
45*dd32d6b2SMartin Matuska# Global used in set_led debug print
46*dd32d6b2SMartin Matuskavdev=""
47*dd32d6b2SMartin Matuska
48*dd32d6b2SMartin Matuska# check_and_set_led (file, val)
49*dd32d6b2SMartin Matuska#
50*dd32d6b2SMartin Matuska# Read an enclosure sysfs file, and write it if it's not already set to 'val'
51*dd32d6b2SMartin Matuska#
52*dd32d6b2SMartin Matuska# Arguments
53*dd32d6b2SMartin Matuska#   file: sysfs file to set (like /sys/class/enclosure/0:0:1:0/SLOT 10/fault)
54*dd32d6b2SMartin Matuska#   val: value to set it to
55*dd32d6b2SMartin Matuska#
56*dd32d6b2SMartin Matuska# Return
57*dd32d6b2SMartin Matuska#  0 on success, 3 on missing sysfs path
58*dd32d6b2SMartin Matuska#
59*dd32d6b2SMartin Matuskacheck_and_set_led()
60*dd32d6b2SMartin Matuska{
61*dd32d6b2SMartin Matuska	file="$1"
62*dd32d6b2SMartin Matuska	val="$2"
63*dd32d6b2SMartin Matuska
64*dd32d6b2SMartin Matuska	if [ -z "$val" ]; then
65*dd32d6b2SMartin Matuska		return 0
66*dd32d6b2SMartin Matuska	fi
67*dd32d6b2SMartin Matuska
68*dd32d6b2SMartin Matuska	if [ ! -e "$file" ] ; then
69*dd32d6b2SMartin Matuska		return 3
70*dd32d6b2SMartin Matuska	fi
71*dd32d6b2SMartin Matuska
72*dd32d6b2SMartin Matuska	# If another process is accessing the LED when we attempt to update it,
73*dd32d6b2SMartin Matuska	# the update will be lost so retry until the LED actually changes or we
74*dd32d6b2SMartin Matuska	# timeout.
75*dd32d6b2SMartin Matuska	for _ in 1 2 3 4 5; do
76*dd32d6b2SMartin Matuska		# We want to check the current state first, since writing to the
77*dd32d6b2SMartin Matuska		# 'fault' entry always causes a SES command, even if the
78*dd32d6b2SMartin Matuska		# current state is already what you want.
79*dd32d6b2SMartin Matuska		read -r current < "${file}"
80*dd32d6b2SMartin Matuska
81*dd32d6b2SMartin Matuska		# On some enclosures if you write 1 to fault, and read it back,
82*dd32d6b2SMartin Matuska		# it will return 2.  Treat all non-zero values as 1 for
83*dd32d6b2SMartin Matuska		# simplicity.
84*dd32d6b2SMartin Matuska		if [ "$current" != "0" ] ; then
85*dd32d6b2SMartin Matuska			current=1
86*dd32d6b2SMartin Matuska		fi
87*dd32d6b2SMartin Matuska
88*dd32d6b2SMartin Matuska		if [ "$current" != "$val" ] ; then
89*dd32d6b2SMartin Matuska			echo "$val" > "$file"
90*dd32d6b2SMartin Matuska			zed_log_msg "vdev $vdev set '$file' LED to $val"
91*dd32d6b2SMartin Matuska		else
92*dd32d6b2SMartin Matuska			break
93*dd32d6b2SMartin Matuska		fi
94*dd32d6b2SMartin Matuska	done
95*dd32d6b2SMartin Matuska}
96*dd32d6b2SMartin Matuska
97*dd32d6b2SMartin Matuska# Fault LEDs for JBODs and NVMe drives are handled a little differently.
98*dd32d6b2SMartin Matuska#
99*dd32d6b2SMartin Matuska# On JBODs the fault LED is called 'fault' and on a path like this:
100*dd32d6b2SMartin Matuska#
101*dd32d6b2SMartin Matuska#   /sys/class/enclosure/0:0:1:0/SLOT 10/fault
102*dd32d6b2SMartin Matuska#
103*dd32d6b2SMartin Matuska# On NVMe it's called 'attention' and on a path like this:
104*dd32d6b2SMartin Matuska#
105*dd32d6b2SMartin Matuska#   /sys/bus/pci/slot/0/attention
106*dd32d6b2SMartin Matuska#
107*dd32d6b2SMartin Matuska# This function returns the full path to the fault LED file for a given
108*dd32d6b2SMartin Matuska# enclosure/slot directory.
109*dd32d6b2SMartin Matuska#
110*dd32d6b2SMartin Matuskapath_to_led()
111*dd32d6b2SMartin Matuska{
112*dd32d6b2SMartin Matuska	dir=$1
113*dd32d6b2SMartin Matuska	if [ -f "$dir/fault" ] ; then
114*dd32d6b2SMartin Matuska		echo "$dir/fault"
115*dd32d6b2SMartin Matuska	elif [ -f "$dir/attention" ] ; then
116*dd32d6b2SMartin Matuska		echo "$dir/attention"
117*dd32d6b2SMartin Matuska	fi
118*dd32d6b2SMartin Matuska}
119*dd32d6b2SMartin Matuska
120*dd32d6b2SMartin Matuskastate_to_val()
121*dd32d6b2SMartin Matuska{
122*dd32d6b2SMartin Matuska	state="$1"
123*dd32d6b2SMartin Matuska	case "$state" in
124*dd32d6b2SMartin Matuska		FAULTED|DEGRADED|UNAVAIL|REMOVED)
125*dd32d6b2SMartin Matuska			echo 1
126*dd32d6b2SMartin Matuska			;;
127*dd32d6b2SMartin Matuska		ONLINE)
128*dd32d6b2SMartin Matuska			echo 0
129*dd32d6b2SMartin Matuska			;;
130*dd32d6b2SMartin Matuska		*)
131*dd32d6b2SMartin Matuska			echo "invalid state: $state"
132*dd32d6b2SMartin Matuska			;;
133*dd32d6b2SMartin Matuska	esac
134*dd32d6b2SMartin Matuska}
135*dd32d6b2SMartin Matuska
136*dd32d6b2SMartin Matuska#
137*dd32d6b2SMartin Matuska# Given a nvme name like 'nvme0n1', pass back its slot directory
138*dd32d6b2SMartin Matuska# like "/sys/bus/pci/slots/0"
139*dd32d6b2SMartin Matuska#
140*dd32d6b2SMartin Matuskanvme_dev_to_slot()
141*dd32d6b2SMartin Matuska{
142*dd32d6b2SMartin Matuska	dev="$1"
143*dd32d6b2SMartin Matuska
144*dd32d6b2SMartin Matuska	# Get the address "0000:01:00.0"
145*dd32d6b2SMartin Matuska	read -r address < "/sys/class/block/$dev/device/address"
146*dd32d6b2SMartin Matuska
147*dd32d6b2SMartin Matuska	find /sys/bus/pci/slots -regex '.*/[0-9]+/address$' | \
148*dd32d6b2SMartin Matuska		while read -r sys_addr; do
149*dd32d6b2SMartin Matuska			read -r this_address < "$sys_addr"
150*dd32d6b2SMartin Matuska
151*dd32d6b2SMartin Matuska			# The format of address is a little different between
152*dd32d6b2SMartin Matuska			# /sys/class/block/$dev/device/address and
153*dd32d6b2SMartin Matuska			# /sys/bus/pci/slots/
154*dd32d6b2SMartin Matuska			#
155*dd32d6b2SMartin Matuska			# address=           "0000:01:00.0"
156*dd32d6b2SMartin Matuska			# this_address =     "0000:01:00"
157*dd32d6b2SMartin Matuska			#
158*dd32d6b2SMartin Matuska			if echo "$address" | grep -Eq ^"$this_address" ; then
159*dd32d6b2SMartin Matuska				echo "${sys_addr%/*}"
160*dd32d6b2SMartin Matuska				break
161*dd32d6b2SMartin Matuska			fi
162*dd32d6b2SMartin Matuska			done
163*dd32d6b2SMartin Matuska}
164*dd32d6b2SMartin Matuska
165*dd32d6b2SMartin Matuska
166*dd32d6b2SMartin Matuska# process_pool (pool)
167*dd32d6b2SMartin Matuska#
168*dd32d6b2SMartin Matuska# Iterate through a pool and set the vdevs' enclosure slot LEDs to
169*dd32d6b2SMartin Matuska# those vdevs' state.
170*dd32d6b2SMartin Matuska#
171*dd32d6b2SMartin Matuska# Arguments
172*dd32d6b2SMartin Matuska#   pool:	Pool name.
173*dd32d6b2SMartin Matuska#
174*dd32d6b2SMartin Matuska# Return
175*dd32d6b2SMartin Matuska#  0 on success, 3 on missing sysfs path
176*dd32d6b2SMartin Matuska#
177*dd32d6b2SMartin Matuskaprocess_pool()
178*dd32d6b2SMartin Matuska{
179*dd32d6b2SMartin Matuska	pool="$1"
180*dd32d6b2SMartin Matuska
181*dd32d6b2SMartin Matuska	# The output will be the vdevs only (from "grep '/dev/'"):
182*dd32d6b2SMartin Matuska	#
183*dd32d6b2SMartin Matuska	#    U45     ONLINE       0     0     0   /dev/sdk          0
184*dd32d6b2SMartin Matuska	#    U46     ONLINE       0     0     0   /dev/sdm          0
185*dd32d6b2SMartin Matuska	#    U47     ONLINE       0     0     0   /dev/sdn          0
186*dd32d6b2SMartin Matuska	#    U50     ONLINE       0     0     0  /dev/sdbn          0
187*dd32d6b2SMartin Matuska	#
188*dd32d6b2SMartin Matuska	ZPOOL_SCRIPTS_AS_ROOT=1 $ZPOOL status -c upath,fault_led "$pool" | grep '/dev/' | (
189*dd32d6b2SMartin Matuska	rc=0
190*dd32d6b2SMartin Matuska	while read -r vdev state _ _ _ therest; do
191*dd32d6b2SMartin Matuska		# Read out current LED value and path
192*dd32d6b2SMartin Matuska		# Get dev name (like 'sda')
193*dd32d6b2SMartin Matuska		dev=$(basename "$(echo "$therest" | awk '{print $(NF-1)}')")
194*dd32d6b2SMartin Matuska		vdev_enc_sysfs_path=$(realpath "/sys/class/block/$dev/device/enclosure_device"*)
195*dd32d6b2SMartin Matuska		if [ ! -d "$vdev_enc_sysfs_path" ] ; then
196*dd32d6b2SMartin Matuska			# This is not a JBOD disk, but it could be a PCI NVMe drive
197*dd32d6b2SMartin Matuska			vdev_enc_sysfs_path=$(nvme_dev_to_slot "$dev")
198*dd32d6b2SMartin Matuska		fi
199*dd32d6b2SMartin Matuska
200*dd32d6b2SMartin Matuska		current_val=$(echo "$therest" | awk '{print $NF}')
201*dd32d6b2SMartin Matuska
202*dd32d6b2SMartin Matuska		if [ "$current_val" != "0" ] ; then
203*dd32d6b2SMartin Matuska			current_val=1
204*dd32d6b2SMartin Matuska		fi
205*dd32d6b2SMartin Matuska
206*dd32d6b2SMartin Matuska		if [ -z "$vdev_enc_sysfs_path" ] ; then
207*dd32d6b2SMartin Matuska			# Skip anything with no sysfs LED entries
208*dd32d6b2SMartin Matuska			continue
209*dd32d6b2SMartin Matuska		fi
210*dd32d6b2SMartin Matuska
211*dd32d6b2SMartin Matuska		led_path=$(path_to_led "$vdev_enc_sysfs_path")
212*dd32d6b2SMartin Matuska		if [ ! -e "$led_path" ] ; then
213*dd32d6b2SMartin Matuska			rc=3
214*dd32d6b2SMartin Matuska			zed_log_msg "vdev $vdev '$led_path' doesn't exist"
215*dd32d6b2SMartin Matuska			continue
216*dd32d6b2SMartin Matuska		fi
217*dd32d6b2SMartin Matuska
218*dd32d6b2SMartin Matuska		val=$(state_to_val "$state")
219*dd32d6b2SMartin Matuska
220*dd32d6b2SMartin Matuska		if [ "$current_val" = "$val" ] ; then
221*dd32d6b2SMartin Matuska			# LED is already set correctly
222*dd32d6b2SMartin Matuska			continue
223*dd32d6b2SMartin Matuska		fi
224*dd32d6b2SMartin Matuska
225*dd32d6b2SMartin Matuska		if ! check_and_set_led "$led_path" "$val"; then
226*dd32d6b2SMartin Matuska			rc=3
227*dd32d6b2SMartin Matuska		fi
228*dd32d6b2SMartin Matuska	done
229*dd32d6b2SMartin Matuska	exit "$rc"; )
230*dd32d6b2SMartin Matuska}
231*dd32d6b2SMartin Matuska
232*dd32d6b2SMartin Matuskaif [ -n "$ZEVENT_VDEV_ENC_SYSFS_PATH" ] && [ -n "$ZEVENT_VDEV_STATE_STR" ] ; then
233*dd32d6b2SMartin Matuska	# Got a statechange for an individual vdev
234*dd32d6b2SMartin Matuska	val=$(state_to_val "$ZEVENT_VDEV_STATE_STR")
235*dd32d6b2SMartin Matuska	vdev=$(basename "$ZEVENT_VDEV_PATH")
236*dd32d6b2SMartin Matuska	ledpath=$(path_to_led "$ZEVENT_VDEV_ENC_SYSFS_PATH")
237*dd32d6b2SMartin Matuska	check_and_set_led "$ledpath" "$val"
238*dd32d6b2SMartin Matuskaelse
239*dd32d6b2SMartin Matuska	# Process the entire pool
240*dd32d6b2SMartin Matuska	poolname=$(zed_guid_to_pool "$ZEVENT_POOL_GUID")
241*dd32d6b2SMartin Matuska	process_pool "$poolname"
242*dd32d6b2SMartin Matuskafi
243