]> granicus.if.org Git - zfs/blob - cmd/vdev_id/vdev_id
b6752ba1b08b9fbff048f9c4935934c4a80b2b4b
[zfs] / cmd / vdev_id / vdev_id
1 #!/bin/sh
2 #
3 # vdev_id: udev helper to generate user-friendly names for JBOD disks
4 #
5 # This script parses the file /etc/zfs/vdev_id.conf to map a
6 # physical path in a storage topology to a channel name.  The
7 # channel name is combined with a disk enclosure slot number to
8 # create an alias that reflects the physical location of the drive.
9 # This is particularly helpful when it comes to tasks like replacing
10 # failed drives.  Slot numbers may also be re-mapped in case the
11 # default numbering is unsatisfactory.  The drive aliases will be
12 # created as symbolic links in /dev/disk/by-vdev.
13 #
14 # The currently supported topologies are sas_direct and sas_switch.
15 # A multipath mode is supported in which dm-mpath devices are
16 # handled by examining the first-listed running component disk.  In
17 # multipath mode the configuration file should contain a channel
18 # definition with the same name for each path to a given enclosure.
19 #
20 # The alias keyword provides a simple way to map already-existing
21 # device symlinks to more convenient names.  It is suitable for
22 # small, static configurations or for sites that have some automated
23 # way to generate the mapping file.
24 #
25 #
26 # Some example configuration files are given below.
27
28 # #
29 # # Example vdev_id.conf - sas_direct.
30 # #
31 #
32 # multipath     no
33 # topology      sas_direct
34 # phys_per_port 4
35 #
36 # #       PCI_ID  HBA PORT  CHANNEL NAME
37 # channel 85:00.0 1         A
38 # channel 85:00.0 0         B
39 # channel 86:00.0 1         C
40 # channel 86:00.0 0         D
41 #
42 # # Custom mapping for Channel A
43 #
44 # #    Linux      Mapped
45 # #    Slot       Slot      Channel
46 # slot 1          7         A
47 # slot 2          10        A
48 # slot 3          3         A
49 # slot 4          6         A
50 #
51 # # Default mapping for B, C, and D
52 # slot 1          4
53 # slot 2          2
54 # slot 3          1
55 # slot 4          3
56
57 # #
58 # # Example vdev_id.conf - sas_switch
59 # #
60 #
61 # topology      sas_switch
62 #
63 # #       SWITCH PORT  CHANNEL NAME
64 # channel 1            A
65 # channel 2            B
66 # channel 3            C
67 # channel 4            D
68
69 # #
70 # # Example vdev_id.conf - multipath
71 # #
72 #
73 # multipath yes
74 #
75 # #       PCI_ID  HBA PORT  CHANNEL NAME
76 # channel 85:00.0 1         A
77 # channel 85:00.0 0         B
78 # channel 86:00.0 1         A
79 # channel 86:00.0 0         B
80
81 # #
82 # # Example vdev_id.conf - alias
83 # #
84 #
85 # #     by-vdev
86 # #     name     fully qualified or base name of device link
87 # alias d1       /dev/disk/by-id/wwn-0x5000c5002de3b9ca
88 # alias d2       wwn-0x5000c5002def789e
89
90 PATH=/bin:/sbin:/usr/bin:/usr/sbin
91 CONFIG=/etc/zfs/vdev_id.conf
92 PHYS_PER_PORT=
93 DEV=
94 MULTIPATH=
95 TOPOLOGY=
96
97 usage() {
98         cat << EOF
99 Usage: vdev_id [-h]
100        vdev_id <-d device> [-c config_file] [-p phys_per_port]
101                [-g sas_direct|sas_switch] [-m]
102
103   -c    specify name of alernate config file [default=$CONFIG]
104   -d    specify basename of device (i.e. sda)
105   -g    Storage network topology [default="$TOPOLOGY"]
106   -m    Run in multipath mode
107   -p    number of phy's per switch port [default=$PHYS_PER_PORT]
108   -h    show this summary
109 EOF
110         exit 0
111 }
112
113 map_slot() {
114         local LINUX_SLOT=$1
115         local CHANNEL=$2
116         local MAPPED_SLOT=
117
118         MAPPED_SLOT=`awk "\\$1 == \"slot\" && \\$2 == ${LINUX_SLOT} && \
119                         \\$4 ~ /^(${CHANNEL}|)$/ { print \\$3; exit }" $CONFIG`
120         if [ -z "$MAPPED_SLOT" ] ; then
121                 MAPPED_SLOT=$LINUX_SLOT
122         fi
123         printf "%d" ${MAPPED_SLOT}
124 }
125
126 map_channel() {
127         local MAPPED_CHAN=
128         local PCI_ID=$1
129         local PORT=$2
130
131         case $TOPOLOGY in
132                 "sas_switch")
133                 MAPPED_CHAN=`awk "\\$1 == \"channel\" && \\$2 == ${PORT} \
134                         { print \\$3; exit }" $CONFIG`
135                 ;;
136                 "sas_direct")
137                 MAPPED_CHAN=`awk "\\$1 == \"channel\" && \
138                         \\$2 == \"${PCI_ID}\" && \\$3 == ${PORT} \
139                         { print \\$4; exit }" $CONFIG`
140                 ;;
141         esac
142         printf "%s" ${MAPPED_CHAN}
143 }
144
145 sas_handler() {
146         if [ -z "$PHYS_PER_PORT" ] ; then
147                 PHYS_PER_PORT=`awk "\\$1 == \"phys_per_port\" \
148                         {print \\$2; exit}" $CONFIG`
149         fi
150         PHYS_PER_PORT=${PHYS_PER_PORT:-4}
151         if ! echo $PHYS_PER_PORT | grep -q -E '^[0-9]+$' ; then
152                 echo "Error: phys_per_port value $PHYS_PER_PORT is non-numeric"
153                 exit 1
154         fi
155
156         if [ -z "$MULTIPATH_MODE" ] ; then
157                 MULTIPATH_MODE=`awk "\\$1 == \"multipath\" \
158                         {print \\$2; exit}" $CONFIG`
159         fi
160
161         # Use first running component device if we're handling a dm-mpath device
162         if [ "$MULTIPATH_MODE" = "yes" ] ; then
163                 # If udev didn't tell us the UUID via DM_NAME, check /dev/mapper
164                 if [ -z "$DM_NAME" ] ; then
165                         DM_NAME=`ls -l --full-time /dev/mapper |
166                                 awk "/\/$DEV$/{print \\$9}"`
167                 fi
168
169                 # For raw disks udev exports DEVTYPE=partition when
170                 # handling partitions, and the rules can be written to
171                 # take advantage of this to append a -part suffix.  For
172                 # dm devices we get DEVTYPE=disk even for partitions so
173                 # we have to append the -part suffix directly in the
174                 # helper.
175                 if [ "$DEVTYPE" != "partition" ] ; then
176                         PART=`echo $DM_NAME | awk -Fp '/p/{print "-part"$2}'`
177                 fi
178
179                 # Strip off partition information.
180                 DM_NAME=`echo $DM_NAME | sed 's/p[0-9][0-9]*$//'`
181                 if [ -z "$DM_NAME" ] ; then
182                         return
183                 fi
184
185                 # Get the raw scsi device name from multipath -l.  Strip off
186                 # leading pipe symbols to make field numbering consistent.
187                 DEV=`multipath -l $DM_NAME |
188                         awk '/running/{gsub("^[|]"," "); print $3 ; exit}'`
189                 if [ -z "$DEV" ] ; then
190                         return
191                 fi
192         fi
193
194         if echo $DEV | grep -q ^/devices/ ; then
195                 sys_path=$DEV
196         else
197                 sys_path=`udevadm info -q path -p /sys/block/$DEV 2>/dev/null`
198         fi
199
200         # Use positional parameters as an ad-hoc array
201         set -- $(echo "$sys_path" | tr / ' ')
202         num_dirs=$#
203         scsi_host_dir="/sys"
204
205         # Get path up to /sys/.../hostX
206         i=1
207         while [ $i -le $num_dirs ] ; do
208                 d=$(eval echo \${$i})
209                 scsi_host_dir="$scsi_host_dir/$d"
210                 echo $d | grep -q -E '^host[0-9]+$' && break
211                 i=$(($i + 1))
212         done
213
214         if [ $i = $num_dirs ] ; then
215                 return
216         fi
217
218         PCI_ID=$(eval echo \${$(($i -1))} | awk -F: '{print $2":"$3}')
219
220         # In sas_switch mode, the directory four levels beneath
221         # /sys/.../hostX contains symlinks to phy devices that reveal
222         # the switch port number.  In sas_direct mode, the phy links one
223         # directory down reveal the HBA port.
224         port_dir=$scsi_host_dir
225         case $TOPOLOGY in
226                 "sas_switch") j=$(($i + 4)) ;;
227                 "sas_direct") j=$(($i + 1)) ;;
228         esac
229
230         i=$(($i + 1))
231         while [ $i -le $j ] ; do
232                 port_dir="$port_dir/$(eval echo \${$i})"
233                 i=$(($i + 1))
234         done
235
236         PHY=`ls -d $port_dir/phy* 2>/dev/null | head -1 | awk -F: '{print $NF}'`
237         if [ -z "$PHY" ] ; then
238                 return
239         fi
240         PORT=$(( $PHY / $PHYS_PER_PORT ))
241
242         # Look in /sys/.../sas_device/end_device-X for the bay_identifier
243         # attribute.
244         end_device_dir=$port_dir
245         while [ $i -lt $num_dirs ] ; do
246                 d=$(eval echo \${$i})
247                 end_device_dir="$end_device_dir/$d"
248                 if echo $d | grep -q '^end_device' ; then
249                         end_device_dir="$end_device_dir/sas_device/$d"
250                         break
251                 fi
252                 i=$(($i + 1))
253         done
254
255         SLOT=`cat $end_device_dir/bay_identifier 2>/dev/null`
256         if [ -z "$SLOT" ] ; then
257                 return
258         fi
259
260         CHAN=`map_channel $PCI_ID $PORT`
261         SLOT=`map_slot $SLOT $CHAN`
262         if [ -z "$CHAN" ] ; then
263                 return
264         fi
265         echo ${CHAN}${SLOT}${PART}
266 }
267
268 alias_handler () {
269         # Special handling is needed to correctly append a -part suffix
270         # to partitions of device mapper devices.  The DEVTYPE attribute
271         # is normally set to "disk" instead of "partition" in this case,
272         # so the udev rules won't handle that for us as they do for
273         # "plain" block devices.
274         #
275         # For example, we may have the following links for a device and its
276         # partitions,
277         #
278         #  /dev/disk/by-id/dm-name-isw_dibgbfcije_ARRAY0   -> ../../dm-0
279         #  /dev/disk/by-id/dm-name-isw_dibgbfcije_ARRAY0p1 -> ../../dm-1
280         #  /dev/disk/by-id/dm-name-isw_dibgbfcije_ARRAY0p2 -> ../../dm-3
281         #
282         # and the following alias in vdev_id.conf.
283         #
284         #   alias A0 dm-name-isw_dibgbfcije_ARRAY0
285         #
286         # The desired outcome is for the following links to be created
287         # without having explicitly defined aliases for the partitions.
288         #
289         #  /dev/disk/by-vdev/A0       -> ../../dm-0
290         #  /dev/disk/by-vdev/A0-part1 -> ../../dm-1
291         #  /dev/disk/by-vdev/A0-part2 -> ../../dm-3
292         #
293         # Warning: The following grep pattern will misidentify whole-disk
294         #          devices whose names end with 'p' followed by a string of
295         #          digits as partitions, causing alias creation to fail. This
296         #          ambiguity seems unavoidable, so devices using this facility
297         #          must not use such names.
298         local DM_PART=
299         if echo $DM_NAME | grep -q -E 'p[0-9][0-9]*$' ; then
300                 if [ "$DEVTYPE" != "partition" ] ; then
301                         DM_PART=`echo $DM_NAME | awk -Fp '/p/{print "-part"$2}'`
302                 fi
303         fi
304
305         # DEVLINKS attribute must have been populated by already-run udev rules.
306         for link in $DEVLINKS ; do
307                 # Remove partition information to match key of top-level device.
308                 if [ -n "$DM_PART" ] ; then
309                         link=`echo $link | sed 's/p[0-9][0-9]*$//'`
310                 fi
311                 # Check both the fully qualified and the base name of link.
312                 for l in $link `basename $link` ; do
313                         alias=`awk "\\$1 == \"alias\" && \\$3 == \"${l}\" \
314                                         { print \\$2; exit }" $CONFIG`
315                         if [ -n "$alias" ] ; then
316                                 echo ${alias}${DM_PART}
317                                 return
318                         fi
319                 done
320         done
321 }
322
323 while getopts 'c:d:g:mp:h' OPTION; do
324         case ${OPTION} in
325         c)
326                 CONFIG=${OPTARG}
327                 ;;
328         d)
329                 DEV=${OPTARG}
330                 ;;
331         g)
332                 TOPOLOGY=$OPTARG
333                 ;;
334         p)
335                 PHYS_PER_PORT=${OPTARG}
336                 ;;
337         m)
338                 MULTIPATH_MODE=yes
339                 ;;
340         h)
341                 usage
342                 ;;
343         esac
344 done
345
346 if [ ! -r $CONFIG ] ; then
347         exit 0
348 fi
349
350 if [ -z "$DEV" ] ; then
351         echo "Error: missing required option -d"
352         exit 1
353 fi
354
355 if [ -z "$TOPOLOGY" ] ; then
356         TOPOLOGY=`awk "\\$1 == \"topology\" {print \\$2; exit}" $CONFIG`
357 fi
358
359 # First check if an alias was defined for this device.
360 ID_VDEV=`alias_handler`
361
362 if [ -z "$ID_VDEV" ] ; then
363         TOPOLOGY=${TOPOLOGY:-sas_direct}
364         case $TOPOLOGY in
365                 sas_direct|sas_switch)
366                         ID_VDEV=`sas_handler`
367                         ;;
368                 *)
369                         echo "Error: unknown topology $TOPOLOGY"
370                         exit 1
371                         ;;
372         esac
373 fi
374
375 if [ -n "$ID_VDEV" ] ; then
376         echo "ID_VDEV=${ID_VDEV}"
377         echo "ID_VDEV_PATH=disk/by-vdev/${ID_VDEV}"
378 fi