Path: blob/main/sys/contrib/openzfs/cmd/zed/zed.d/statechange-sync-led.sh
48529 views
#!/bin/sh1# shellcheck disable=SC21542#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, then11# only set the LED for that particular vdev. This is the case for statechange12# events and some vdev_* events.13#14# 2. If those vars are not set, then check the state of all vdevs in the pool15# 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 the18# Linux SCSI Enclosure services (SES) driver. The script will do nothing19# if you have no enclosure, or if your enclosure isn't supported.20#21# Exit codes:22# 0: enclosure led successfully set23# 1: enclosure leds not available24# 2: enclosure leds administratively disabled25# 3: The led sysfs path passed from ZFS does not exist26# 4: $ZPOOL not set27# 5: awk is not installed2829[ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc"30. "${ZED_ZEDLET_DIR}/zed-functions.sh"3132if [ ! -d /sys/class/enclosure ] && [ ! -d /sys/bus/pci/slots ] ; then33# No JBOD enclosure or NVMe slots34exit 135fi3637if [ "${ZED_USE_ENCLOSURE_LEDS}" != "1" ] ; then38exit 239fi4041zed_check_cmd "$ZPOOL" || exit 442zed_check_cmd awk || exit 54344# Global used in set_led debug print45vdev=""4647# 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# Arguments52# file: sysfs file to set (like /sys/class/enclosure/0:0:1:0/SLOT 10/fault)53# val: value to set it to54#55# Return56# 0 on success, 3 on missing sysfs path57#58check_and_set_led()59{60file="$1"61val="$2"6263if [ -z "$val" ]; then64return 065fi6667if [ ! -e "$file" ] ; then68return 369fi7071# 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 we73# timeout.74for _ in 1 2 3 4 5; do75# We want to check the current state first, since writing to the76# 'fault' entry always causes a SES command, even if the77# current state is already what you want.78read -r current < "${file}"7980# 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 for82# simplicity.83if [ "$current" != "0" ] ; then84current=185fi8687if [ "$current" != "$val" ] ; then88echo "$val" > "$file"89zed_log_msg "vdev $vdev set '$file' LED to $val"90else91break92fi93done94}9596# 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/fault101#102# On NVMe it's called 'attention' and on a path like this:103#104# /sys/bus/pci/slot/0/attention105#106# This function returns the full path to the fault LED file for a given107# enclosure/slot directory.108#109path_to_led()110{111dir=$1112if [ -f "$dir/fault" ] ; then113echo "$dir/fault"114elif [ -f "$dir/attention" ] ; then115echo "$dir/attention"116fi117}118119state_to_val()120{121state="$1"122case "$state" in123FAULTED|DEGRADED|UNAVAIL|REMOVED)124echo 1125;;126ONLINE)127echo 0128;;129*)130echo "invalid state: $state"131;;132esac133}134135#136# Given a nvme name like 'nvme0n1', pass back its slot directory137# like "/sys/bus/pci/slots/0"138#139nvme_dev_to_slot()140{141dev="$1"142143# Get the address "0000:01:00.0"144read -r address < "/sys/class/block/$dev/device/address"145146find /sys/bus/pci/slots -regex '.*/[0-9]+/address$' | \147while read -r sys_addr; do148read -r this_address < "$sys_addr"149150# The format of address is a little different between151# /sys/class/block/$dev/device/address and152# /sys/bus/pci/slots/153#154# address= "0000:01:00.0"155# this_address = "0000:01:00"156#157if echo "$address" | grep -Eq ^"$this_address" ; then158echo "${sys_addr%/*}"159break160fi161done162}163164165# process_pool (pool)166#167# Iterate through a pool and set the vdevs' enclosure slot LEDs to168# those vdevs' state.169#170# Arguments171# pool: Pool name.172#173# Return174# 0 on success, 3 on missing sysfs path175#176process_pool()177{178pool="$1"179180# The output will be the vdevs only (from "grep '/dev/'"):181#182# U45 ONLINE 0 0 0 /dev/sdk 0183# U46 ONLINE 0 0 0 /dev/sdm 0184# U47 ONLINE 0 0 0 /dev/sdn 0185# U50 ONLINE 0 0 0 /dev/sdbn 0186#187ZPOOL_SCRIPTS_AS_ROOT=1 $ZPOOL status -c upath,fault_led "$pool" | grep '/dev/' | (188rc=0189while read -r vdev state _ _ _ therest; do190# Read out current LED value and path191# Get dev name (like 'sda')192dev=$(basename "$(echo "$therest" | awk '{print $(NF-1)}')")193vdev_enc_sysfs_path=$(realpath "/sys/class/block/$dev/device/enclosure_device"*)194if [ ! -d "$vdev_enc_sysfs_path" ] ; then195# This is not a JBOD disk, but it could be a PCI NVMe drive196vdev_enc_sysfs_path=$(nvme_dev_to_slot "$dev")197fi198199current_val=$(echo "$therest" | awk '{print $NF}')200201if [ "$current_val" != "0" ] ; then202current_val=1203fi204205if [ -z "$vdev_enc_sysfs_path" ] ; then206# Skip anything with no sysfs LED entries207continue208fi209210led_path=$(path_to_led "$vdev_enc_sysfs_path")211if [ ! -e "$led_path" ] ; then212rc=3213zed_log_msg "vdev $vdev '$led_path' doesn't exist"214continue215fi216217val=$(state_to_val "$state")218219if [ "$current_val" = "$val" ] ; then220# LED is already set correctly221continue222fi223224if ! check_and_set_led "$led_path" "$val"; then225rc=3226fi227done228exit "$rc"; )229}230231if [ -n "$ZEVENT_VDEV_ENC_SYSFS_PATH" ] && [ -n "$ZEVENT_VDEV_STATE_STR" ] ; then232# Got a statechange for an individual vdev233val=$(state_to_val "$ZEVENT_VDEV_STATE_STR")234vdev=$(basename "$ZEVENT_VDEV_PATH")235ledpath=$(path_to_led "$ZEVENT_VDEV_ENC_SYSFS_PATH")236check_and_set_led "$ledpath" "$val"237else238# Process the entire pool239poolname=$(zed_guid_to_pool "$ZEVENT_POOL_GUID")240process_pool "$poolname"241fi242243244