No point in rewind() mtab in zfs_unshare_proto(). We're not really
[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 "\\$1 == \"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 "\\$1 == \"channel\" && \\$2 == ${PORT} \
131                         { print \\$3; exit }" $CONFIG`
132                 ;;
133                 "sas_direct")
134                 MAPPED_CHAN=`awk "\\$1 == \"channel\" && \
135                         \\$2 == \"${PCI_ID}\" && \\$3 == ${PORT} \
136                         { print \\$4; exit }" $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 "\\$1 == \"phys_per_port\" \
145                         {print \\$2; exit}" $CONFIG`
146         fi
147         PHYS_PER_PORT=${PHYS_PER_PORT:-4}
148         if ! echo $PHYS_PER_PORT | grep -q -E '^[0-9]+$' ; then
149                 echo "Error: phys_per_port value $PHYS_PER_PORT is non-numeric"
150                 exit 1
151         fi
152
153         if [ -z "$MULTIPATH_MODE" ] ; then
154                 MULTIPATH_MODE=`awk "\\$1 == \"multipath\" \
155                         {print \\$2; exit}" $CONFIG`
156         fi
157
158         # Use first running component device if we're handling a dm-mpath device
159         if [ "$MULTIPATH_MODE" = "yes" ] ; then
160                 # If udev didn't tell us the UUID via DM_NAME, check /dev/mapper
161                 if [ -z "$DM_NAME" ] ; then
162                         DM_NAME=`ls -l --full-time /dev/mapper |
163                                 awk "/\/$DEV$/{print \\$9}"`
164                 fi
165
166                 # For raw disks udev exports DEVTYPE=partition when
167                 # handling partitions, and the rules can be written to
168                 # take advantage of this to append a -part suffix.  For
169                 # dm devices we get DEVTYPE=disk even for partitions so
170                 # we have to append the -part suffix directly in the
171                 # helper.
172                 if [ "$DEVTYPE" != "partition" ] ; then
173                         PART=`echo $DM_NAME | awk -Fp '/p/{print "-part"$2}'`
174                 fi
175
176                 # Strip off partition information.
177                 DM_NAME=`echo $DM_NAME | sed 's/p[0-9][0-9]*$//'`
178                 if [ -z "$DM_NAME" ] ; then
179                         return
180                 fi
181
182                 # Get the raw scsi device name from multipath -l.
183                 DEV=`multipath -l $DM_NAME |awk '/running/{print $3 ; exit}'`
184                 if [ -z "$DEV" ] ; then
185                         return
186                 fi
187         fi
188
189         if echo $DEV | grep -q ^/devices/ ; then
190                 sys_path=$DEV
191         else
192                 sys_path=`udevadm info -q path -p /sys/block/$DEV 2>/dev/null`
193         fi
194
195         # Use positional parameters as an ad-hoc array
196         set -- $(echo "$sys_path" | tr / ' ')
197         num_dirs=$#
198         scsi_host_dir="/sys"
199
200         # Get path up to /sys/.../hostX
201         i=1
202         while [ $i -le $num_dirs ] ; do
203                 d=$(eval echo \${$i})
204                 scsi_host_dir="$scsi_host_dir/$d"
205                 echo $d | grep -q -E '^host[0-9]+$' && break
206                 i=$(($i + 1))
207         done
208
209         if [ $i = $num_dirs ] ; then
210                 return
211         fi
212
213         PCI_ID=$(eval echo \${$(($i -1))} | awk -F: '{print $2":"$3}')
214
215         # In sas_switch mode, the directory four levels beneath
216         # /sys/.../hostX contains symlinks to phy devices that reveal
217         # the switch port number.  In sas_direct mode, the phy links one
218         # directory down reveal the HBA port.
219         port_dir=$scsi_host_dir
220         case $TOPOLOGY in
221                 "sas_switch") j=$(($i + 4)) ;;
222                 "sas_direct") j=$(($i + 1)) ;;
223         esac
224
225         i=$(($i + 1))
226         while [ $i -le $j ] ; do
227                 port_dir="$port_dir/$(eval echo \${$i})"
228                 i=$(($i + 1))
229         done
230
231         PHY=`ls -d $port_dir/phy* 2>/dev/null | head -1 | awk -F: '{print $NF}'`
232         if [ -z "$PHY" ] ; then
233                 return
234         fi
235         PORT=$(( $PHY / $PHYS_PER_PORT ))
236
237         # Look in /sys/.../sas_device/end_device-X for the bay_identifier
238         # attribute.
239         end_device_dir=$port_dir
240         while [ $i -lt $num_dirs ] ; do
241                 d=$(eval echo \${$i})
242                 end_device_dir="$end_device_dir/$d"
243                 if echo $d | grep -q '^end_device' ; then
244                         end_device_dir="$end_device_dir/sas_device/$d"
245                         break
246                 fi
247                 i=$(($i + 1))
248         done
249
250         SLOT=`cat $end_device_dir/bay_identifier 2>/dev/null`
251         if [ -z "$SLOT" ] ; then
252                 return
253         fi
254
255         SLOT=`map_slot $SLOT`
256         CHAN=`map_channel $PCI_ID $PORT`
257         if [ -z "$CHAN" ] ; then
258                 return
259         fi
260         echo ${CHAN}${SLOT}${PART}
261 }
262
263 alias_handler () {
264         # Special handling is needed to correctly append a -part suffix
265         # to partitions of device mapper devices.  The DEVTYPE attribute
266         # is normally set to "disk" instead of "partition" in this case,
267         # so the udev rules won't handle that for us as they do for
268         # "plain" block devices.
269         #
270         # For example, we may have the following links for a device and its
271         # partitions,
272         #
273         #  /dev/disk/by-id/dm-name-isw_dibgbfcije_ARRAY0   -> ../../dm-0
274         #  /dev/disk/by-id/dm-name-isw_dibgbfcije_ARRAY0p1 -> ../../dm-1
275         #  /dev/disk/by-id/dm-name-isw_dibgbfcije_ARRAY0p2 -> ../../dm-3
276         #
277         # and the following alias in vdev_id.conf.
278         #
279         #   alias A0 dm-name-isw_dibgbfcije_ARRAY0
280         #
281         # The desired outcome is for the following links to be created
282         # without having explicitly defined aliases for the partitions.
283         #
284         #  /dev/disk/by-vdev/A0       -> ../../dm-0
285         #  /dev/disk/by-vdev/A0-part1 -> ../../dm-1
286         #  /dev/disk/by-vdev/A0-part2 -> ../../dm-3
287         #
288         # Warning: The following grep pattern will misidentify whole-disk
289         #          devices whose names end with 'p' followed by a string of
290         #          digits as partitions, causing alias creation to fail. This
291         #          ambiguity seems unavoidable, so devices using this facility
292         #          must not use such names.
293         local DM_PART=
294         if echo $DM_NAME | grep -q -E 'p[0-9][0-9]*$' ; then
295                 if [ "$DEVTYPE" != "partition" ] ; then
296                         DM_PART=`echo $DM_NAME | awk -Fp '/p/{print "-part"$2}'`
297                 fi
298         fi
299
300         # DEVLINKS attribute must have been populated by already-run udev rules.
301         for link in $DEVLINKS ; do
302                 # Remove partition information to match key of top-level device.
303                 if [ -n "$DM_PART" ] ; then
304                         link=`echo $link | sed 's/p[0-9][0-9]*$//'`
305                 fi
306                 # Check both the fully qualified and the base name of link.
307                 for l in $link `basename $link` ; do
308                         alias=`awk "\\$1 == \"alias\" && \\$3 == \"${l}\" \
309                                         { print \\$2; exit }" $CONFIG`
310                         if [ -n "$alias" ] ; then
311                                 echo ${alias}${DM_PART}
312                                 return
313                         fi
314                 done
315         done
316 }
317
318 while getopts 'c:d:g:mp:h' OPTION; do
319         case ${OPTION} in
320         c)
321                 CONFIG=${OPTARG}
322                 ;;
323         d)
324                 DEV=${OPTARG}
325                 ;;
326         g)
327                 TOPOLOGY=$OPTARG
328                 ;;
329         p)
330                 PHYS_PER_PORT=${OPTARG}
331                 ;;
332         m)
333                 MULTIPATH_MODE=yes
334                 ;;
335         h)
336                 usage
337                 ;;
338         esac
339 done
340
341 if [ ! -r $CONFIG ] ; then
342         exit 0
343 fi
344
345 if [ -z "$DEV" ] ; then
346         echo "Error: missing required option -d"
347         exit 1
348 fi
349
350 if [ -z "$TOPOLOGY" ] ; then
351         TOPOLOGY=`awk "\\$1 == \"topology\" {print \\$2; exit}" $CONFIG`
352 fi
353
354 # First check if an alias was defined for this device.
355 ID_VDEV=`alias_handler`
356
357 if [ -z "$ID_VDEV" ] ; then
358         TOPOLOGY=${TOPOLOGY:-sas_direct}
359         case $TOPOLOGY in
360                 sas_direct|sas_switch)
361                         ID_VDEV=`sas_handler`
362                         ;;
363                 *)
364                         echo "Error: unknown topology $TOPOLOGY"
365                         exit 1
366                         ;;
367         esac
368 fi
369
370 if [ -n "$ID_VDEV" ] ; then
371         echo "ID_VDEV=${ID_VDEV}"
372         echo "ID_VDEV_PATH=disk/by-vdev/${ID_VDEV}"
373 fi