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