1eda14cbcSMatt Macy#!/bin/sh
2e92ffd9bSMartin Matuska# shellcheck disable=SC2154
3eda14cbcSMatt Macy#
416038816SMartin Matuska# Turn off/on vdevs' enclosure fault LEDs when their pool's state changes.
5eda14cbcSMatt Macy#
616038816SMartin Matuska# Turn a vdev's fault LED on if it becomes FAULTED, DEGRADED or UNAVAIL.
716038816SMartin Matuska# Turn its LED off when it's back ONLINE again.
8eda14cbcSMatt Macy#
9eda14cbcSMatt Macy# This script run in two basic modes:
10eda14cbcSMatt Macy#
11eda14cbcSMatt Macy# 1. If $ZEVENT_VDEV_ENC_SYSFS_PATH and $ZEVENT_VDEV_STATE_STR are set, then
1216038816SMartin Matuska# only set the LED for that particular vdev. This is the case for statechange
13eda14cbcSMatt Macy# events and some vdev_* events.
14eda14cbcSMatt Macy#
1516038816SMartin Matuska# 2. If those vars are not set, then check the state of all vdevs in the pool
16eda14cbcSMatt Macy# and set the LEDs accordingly.  This is the case for pool_import events.
17eda14cbcSMatt Macy#
18eda14cbcSMatt Macy# Note that this script requires that your enclosure be supported by the
1916038816SMartin Matuska# Linux SCSI Enclosure services (SES) driver.  The script will do nothing
20eda14cbcSMatt Macy# if you have no enclosure, or if your enclosure isn't supported.
21eda14cbcSMatt Macy#
22eda14cbcSMatt Macy# Exit codes:
23eda14cbcSMatt Macy#   0: enclosure led successfully set
24eda14cbcSMatt Macy#   1: enclosure leds not available
25eda14cbcSMatt Macy#   2: enclosure leds administratively disabled
26eda14cbcSMatt Macy#   3: The led sysfs path passed from ZFS does not exist
27eda14cbcSMatt Macy#   4: $ZPOOL not set
28eda14cbcSMatt Macy#   5: awk is not installed
29eda14cbcSMatt Macy
30eda14cbcSMatt Macy[ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc"
31eda14cbcSMatt Macy. "${ZED_ZEDLET_DIR}/zed-functions.sh"
32eda14cbcSMatt Macy
3381b22a98SMartin Matuskaif [ ! -d /sys/class/enclosure ] && [ ! -d /sys/bus/pci/slots ] ; then
3481b22a98SMartin Matuska	# No JBOD enclosure or NVMe slots
35eda14cbcSMatt Macy	exit 1
36eda14cbcSMatt Macyfi
37eda14cbcSMatt Macy
38eda14cbcSMatt Macyif [ "${ZED_USE_ENCLOSURE_LEDS}" != "1" ] ; then
39eda14cbcSMatt Macy	exit 2
40eda14cbcSMatt Macyfi
41eda14cbcSMatt Macy
42eda14cbcSMatt Macyzed_check_cmd "$ZPOOL" || exit 4
43eda14cbcSMatt Macyzed_check_cmd awk || exit 5
44eda14cbcSMatt Macy
45eda14cbcSMatt Macy# Global used in set_led debug print
46eda14cbcSMatt Macyvdev=""
47eda14cbcSMatt Macy
48eda14cbcSMatt Macy# check_and_set_led (file, val)
49eda14cbcSMatt Macy#
50eda14cbcSMatt Macy# Read an enclosure sysfs file, and write it if it's not already set to 'val'
51eda14cbcSMatt Macy#
52eda14cbcSMatt Macy# Arguments
53eda14cbcSMatt Macy#   file: sysfs file to set (like /sys/class/enclosure/0:0:1:0/SLOT 10/fault)
54eda14cbcSMatt Macy#   val: value to set it to
55eda14cbcSMatt Macy#
56eda14cbcSMatt Macy# Return
57eda14cbcSMatt Macy#  0 on success, 3 on missing sysfs path
58eda14cbcSMatt Macy#
59eda14cbcSMatt Macycheck_and_set_led()
60eda14cbcSMatt Macy{
61eda14cbcSMatt Macy	file="$1"
62eda14cbcSMatt Macy	val="$2"
63eda14cbcSMatt Macy
6416038816SMartin Matuska	if [ -z "$val" ]; then
6516038816SMartin Matuska		return 0
6616038816SMartin Matuska	fi
6716038816SMartin Matuska
68eda14cbcSMatt Macy	if [ ! -e "$file" ] ; then
69eda14cbcSMatt Macy		return 3
70eda14cbcSMatt Macy	fi
71eda14cbcSMatt Macy
72eda14cbcSMatt Macy	# If another process is accessing the LED when we attempt to update it,
73eda14cbcSMatt Macy	# the update will be lost so retry until the LED actually changes or we
74eda14cbcSMatt Macy	# timeout.
7516038816SMartin Matuska	for _ in 1 2 3 4 5; do
76eda14cbcSMatt Macy		# We want to check the current state first, since writing to the
77eda14cbcSMatt Macy		# 'fault' entry always causes a SES command, even if the
78eda14cbcSMatt Macy		# current state is already what you want.
7916038816SMartin Matuska		read -r current < "${file}"
80eda14cbcSMatt Macy
81eda14cbcSMatt Macy		# On some enclosures if you write 1 to fault, and read it back,
82eda14cbcSMatt Macy		# it will return 2.  Treat all non-zero values as 1 for
83eda14cbcSMatt Macy		# simplicity.
84eda14cbcSMatt Macy		if [ "$current" != "0" ] ; then
85eda14cbcSMatt Macy			current=1
86eda14cbcSMatt Macy		fi
87eda14cbcSMatt Macy
88eda14cbcSMatt Macy		if [ "$current" != "$val" ] ; then
89eda14cbcSMatt Macy			echo "$val" > "$file"
90eda14cbcSMatt Macy			zed_log_msg "vdev $vdev set '$file' LED to $val"
91eda14cbcSMatt Macy		else
92eda14cbcSMatt Macy			break
93eda14cbcSMatt Macy		fi
94eda14cbcSMatt Macy	done
95eda14cbcSMatt Macy}
96eda14cbcSMatt Macy
9781b22a98SMartin Matuska# Fault LEDs for JBODs and NVMe drives are handled a little differently.
9881b22a98SMartin Matuska#
9981b22a98SMartin Matuska# On JBODs the fault LED is called 'fault' and on a path like this:
10081b22a98SMartin Matuska#
10181b22a98SMartin Matuska#   /sys/class/enclosure/0:0:1:0/SLOT 10/fault
10281b22a98SMartin Matuska#
10381b22a98SMartin Matuska# On NVMe it's called 'attention' and on a path like this:
10481b22a98SMartin Matuska#
10581b22a98SMartin Matuska#   /sys/bus/pci/slot/0/attention
10681b22a98SMartin Matuska#
10781b22a98SMartin Matuska# This function returns the full path to the fault LED file for a given
10881b22a98SMartin Matuska# enclosure/slot directory.
10981b22a98SMartin Matuska#
11081b22a98SMartin Matuskapath_to_led()
11181b22a98SMartin Matuska{
11281b22a98SMartin Matuska	dir=$1
11381b22a98SMartin Matuska	if [ -f "$dir/fault" ] ; then
11481b22a98SMartin Matuska		echo "$dir/fault"
11581b22a98SMartin Matuska	elif [ -f "$dir/attention" ] ; then
11681b22a98SMartin Matuska		echo "$dir/attention"
11781b22a98SMartin Matuska	fi
11881b22a98SMartin Matuska}
11981b22a98SMartin Matuska
120eda14cbcSMatt Macystate_to_val()
121eda14cbcSMatt Macy{
122eda14cbcSMatt Macy	state="$1"
12316038816SMartin Matuska	case "$state" in
124*cbfe9975SMartin Matuska		FAULTED|DEGRADED|UNAVAIL|REMOVED)
125eda14cbcSMatt Macy			echo 1
12616038816SMartin Matuska			;;
12716038816SMartin Matuska		ONLINE)
128eda14cbcSMatt Macy			echo 0
12916038816SMartin Matuska			;;
130e92ffd9bSMartin Matuska		*)
131e92ffd9bSMartin Matuska			echo "invalid state: $state"
132e92ffd9bSMartin Matuska			;;
13316038816SMartin Matuska	esac
134eda14cbcSMatt Macy}
135eda14cbcSMatt Macy
13681b22a98SMartin Matuska#
13781b22a98SMartin Matuska# Given a nvme name like 'nvme0n1', pass back its slot directory
13881b22a98SMartin Matuska# like "/sys/bus/pci/slots/0"
13981b22a98SMartin Matuska#
14081b22a98SMartin Matuskanvme_dev_to_slot()
14181b22a98SMartin Matuska{
14281b22a98SMartin Matuska	dev="$1"
14381b22a98SMartin Matuska
14481b22a98SMartin Matuska	# Get the address "0000:01:00.0"
145e92ffd9bSMartin Matuska	read -r address < "/sys/class/block/$dev/device/address"
14681b22a98SMartin Matuska
147e92ffd9bSMartin Matuska	find /sys/bus/pci/slots -regex '.*/[0-9]+/address$' | \
148e92ffd9bSMartin Matuska		while read -r sys_addr; do
149e92ffd9bSMartin Matuska			read -r this_address < "$sys_addr"
15081b22a98SMartin Matuska
15181b22a98SMartin Matuska			# The format of address is a little different between
15281b22a98SMartin Matuska			# /sys/class/block/$dev/device/address and
15381b22a98SMartin Matuska			# /sys/bus/pci/slots/
15481b22a98SMartin Matuska			#
15581b22a98SMartin Matuska			# address=           "0000:01:00.0"
15681b22a98SMartin Matuska			# this_address =     "0000:01:00"
15781b22a98SMartin Matuska			#
15881b22a98SMartin Matuska			if echo "$address" | grep -Eq ^"$this_address" ; then
159e92ffd9bSMartin Matuska				echo "${sys_addr%/*}"
16081b22a98SMartin Matuska				break
16181b22a98SMartin Matuska			fi
16281b22a98SMartin Matuska			done
16381b22a98SMartin Matuska}
16481b22a98SMartin Matuska
16581b22a98SMartin Matuska
16616038816SMartin Matuska# process_pool (pool)
167eda14cbcSMatt Macy#
16816038816SMartin Matuska# Iterate through a pool and set the vdevs' enclosure slot LEDs to
16916038816SMartin Matuska# those vdevs' state.
170eda14cbcSMatt Macy#
171eda14cbcSMatt Macy# Arguments
17216038816SMartin Matuska#   pool:	Pool name.
173eda14cbcSMatt Macy#
174eda14cbcSMatt Macy# Return
175eda14cbcSMatt Macy#  0 on success, 3 on missing sysfs path
176eda14cbcSMatt Macy#
177eda14cbcSMatt Macyprocess_pool()
178eda14cbcSMatt Macy{
179eda14cbcSMatt Macy	pool="$1"
18016038816SMartin Matuska
18116038816SMartin Matuska	# The output will be the vdevs only (from "grep '/dev/'"):
18216038816SMartin Matuska	#
18316038816SMartin Matuska	#    U45     ONLINE       0     0     0   /dev/sdk          0
18416038816SMartin Matuska	#    U46     ONLINE       0     0     0   /dev/sdm          0
18516038816SMartin Matuska	#    U47     ONLINE       0     0     0   /dev/sdn          0
18616038816SMartin Matuska	#    U50     ONLINE       0     0     0  /dev/sdbn          0
18716038816SMartin Matuska	#
18816038816SMartin Matuska	ZPOOL_SCRIPTS_AS_ROOT=1 $ZPOOL status -c upath,fault_led "$pool" | grep '/dev/' | (
189eda14cbcSMatt Macy	rc=0
19016038816SMartin Matuska	while read -r vdev state _ _ _ therest; do
191eda14cbcSMatt Macy		# Read out current LED value and path
19216038816SMartin Matuska		# Get dev name (like 'sda')
19316038816SMartin Matuska		dev=$(basename "$(echo "$therest" | awk '{print $(NF-1)}')")
19416038816SMartin Matuska		vdev_enc_sysfs_path=$(realpath "/sys/class/block/$dev/device/enclosure_device"*)
19581b22a98SMartin Matuska		if [ ! -d "$vdev_enc_sysfs_path" ] ; then
19681b22a98SMartin Matuska			# This is not a JBOD disk, but it could be a PCI NVMe drive
19781b22a98SMartin Matuska			vdev_enc_sysfs_path=$(nvme_dev_to_slot "$dev")
19881b22a98SMartin Matuska		fi
19981b22a98SMartin Matuska
20016038816SMartin Matuska		current_val=$(echo "$therest" | awk '{print $NF}')
201eda14cbcSMatt Macy
202eda14cbcSMatt Macy		if [ "$current_val" != "0" ] ; then
203eda14cbcSMatt Macy			current_val=1
204eda14cbcSMatt Macy		fi
205eda14cbcSMatt Macy
206eda14cbcSMatt Macy		if [ -z "$vdev_enc_sysfs_path" ] ; then
207eda14cbcSMatt Macy			# Skip anything with no sysfs LED entries
208eda14cbcSMatt Macy			continue
209eda14cbcSMatt Macy		fi
210eda14cbcSMatt Macy
21181b22a98SMartin Matuska		led_path=$(path_to_led "$vdev_enc_sysfs_path")
21281b22a98SMartin Matuska		if [ ! -e "$led_path" ] ; then
21316038816SMartin Matuska			rc=3
21481b22a98SMartin Matuska			zed_log_msg "vdev $vdev '$led_path' doesn't exist"
21516038816SMartin Matuska			continue
216eda14cbcSMatt Macy		fi
217eda14cbcSMatt Macy
218eda14cbcSMatt Macy		val=$(state_to_val "$state")
219eda14cbcSMatt Macy
220eda14cbcSMatt Macy		if [ "$current_val" = "$val" ] ; then
221eda14cbcSMatt Macy			# LED is already set correctly
22216038816SMartin Matuska			continue
223eda14cbcSMatt Macy		fi
224eda14cbcSMatt Macy
22581b22a98SMartin Matuska		if ! check_and_set_led "$led_path" "$val"; then
22616038816SMartin Matuska			rc=3
227eda14cbcSMatt Macy		fi
228eda14cbcSMatt Macy	done
22916038816SMartin Matuska	exit "$rc"; )
230eda14cbcSMatt Macy}
231eda14cbcSMatt Macy
232eda14cbcSMatt Macyif [ -n "$ZEVENT_VDEV_ENC_SYSFS_PATH" ] && [ -n "$ZEVENT_VDEV_STATE_STR" ] ; then
23316038816SMartin Matuska	# Got a statechange for an individual vdev
234eda14cbcSMatt Macy	val=$(state_to_val "$ZEVENT_VDEV_STATE_STR")
235eda14cbcSMatt Macy	vdev=$(basename "$ZEVENT_VDEV_PATH")
23681b22a98SMartin Matuska	ledpath=$(path_to_led "$ZEVENT_VDEV_ENC_SYSFS_PATH")
23781b22a98SMartin Matuska	check_and_set_led "$ledpath" "$val"
238eda14cbcSMatt Macyelse
239eda14cbcSMatt Macy	# Process the entire pool
240eda14cbcSMatt Macy	poolname=$(zed_guid_to_pool "$ZEVENT_POOL_GUID")
241eda14cbcSMatt Macy	process_pool "$poolname"
242eda14cbcSMatt Macyfi
243