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