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