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
33 if [ ! -d /sys/class/enclosure ] && [ ! -d /sys/bus/pci/slots ] ; then
34 # No JBOD enclosure or NVMe slots
35 exit 1
36 fi
37
38 if [ "${ZED_USE_ENCLOSURE_LEDS}" != "1" ] ; then
39 exit 2
40 fi
41
42 zed_check_cmd "$ZPOOL" || exit 4
43 zed_check_cmd awk || exit 5
44
45 # Global used in set_led debug print
46 vdev=""
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 #
59 check_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 #
110 path_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
120 state_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 #
140 nvme_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 #
177 process_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
232 if [ -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"
238 else
239 # Process the entire pool
240 poolname=$(zed_guid_to_pool "$ZEVENT_POOL_GUID")
241 process_pool "$poolname"
242 fi
Cache object: 0875b93356460629275a0cfe666db48c
|