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