Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/sys/contrib/openzfs/cmd/zed/zed.d/statechange-sync-led.sh
48529 views
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|REMOVED)
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
243
244