Make vdev_id POSIX sh compatible
[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 only currently supported topologies are sas_direct and
15 # sas_switch.  A multipath mode is supported in which dm-mpath
16 # devices are handled by examining the first-listed running
17 # component disk.  In multipath mode the configuration file
18 # should contain a channel definition with the same name for
19 # each path to a given enclosure.
20
21 #
22 # Some example configuration files are given below.
23
24 # #
25 # # Example vdev_id.conf - sas_direct.
26 # #
27 #
28 # multipath     no
29 # topology      sas_direct
30 # phys_per_port 4
31 #
32 # #       PCI_ID  HBA PORT  CHANNEL NAME
33 # channel 85:00.0 1         A
34 # channel 85:00.0 0         B
35 # channel 86:00.0 1         C
36 # channel 86:00.0 0         D
37 #
38 # #    Linux    Mapped
39 # #    Slot     Slot
40 # slot 1        7
41 # slot 2        10
42 # slot 3        3
43 # slot 4        6
44 # slot 5        2
45 # slot 6        8
46 # slot 7        1
47 # slot 8        4
48 # slot 9        9
49 # slot 10       5
50
51 # #
52 # # Example vdev_id.conf - sas_switch
53 # #
54 #
55 # topology      sas_switch
56 #
57 # #       SWITCH PORT  CHANNEL NAME
58 # channel 1            A
59 # channel 2            B
60 # channel 3            C
61 # channel 4            D
62
63 # #
64 # # Example vdev_id.conf - multipath
65 # #
66 #
67 # multipath yes
68 #
69 # #       PCI_ID  HBA PORT  CHANNEL NAME
70 # channel 85:00.0 1         A
71 # channel 85:00.0 0         B
72 # channel 86:00.0 1         A
73 # channel 86:00.0 0         B
74
75 PATH=/bin:/sbin:/usr/bin:/usr/sbin
76 CONFIG=/etc/zfs/vdev_id.conf
77 PHYS_PER_PORT=
78 DEV=
79 SLOT_MAP=
80 CHANNEL_MAP=
81 MULTIPATH=
82 TOPOLOGY=
83
84 usage() {
85         cat << EOF
86 Usage: vdev_id [-h]
87        vdev_id <-d device> [-c config_file] [-p phys_per_port]
88                [-g sas_direct|sas_switch] [-m]
89
90   -c    specify name of alernate config file [default=$CONFIG]
91   -d    specify basename of device (i.e. sda)
92   -g    Storage network topology [default="$TOPOLOGY"]
93   -m    Run in multipath mode
94   -p    number of phy's per switch port [default=$PHYS_PER_PORT]
95   -h    show this summary
96 EOF
97         exit 0
98 }
99
100 map_slot() {
101         local LINUX_SLOT=$1
102         local MAPPED_SLOT=
103
104         MAPPED_SLOT=`awk "/^slot / && \\$2 == ${LINUX_SLOT} \
105                         { print \\$3; exit }" $CONFIG`
106         if [ -z "$MAPPED_SLOT" ] ; then
107                 MAPPED_SLOT=$LINUX_SLOT
108         fi
109         printf "%d" ${MAPPED_SLOT}
110 }
111
112 map_channel() {
113         local MAPPED_CHAN=
114         local PCI_ID=$1
115         local PORT=$2
116
117         case $TOPOLOGY in
118                 "sas_switch")
119                 MAPPED_CHAN=`awk "/^channel / && \\$2 == ${PORT} \
120                         { print \\$3; exit }" $CONFIG`
121                 ;;
122                 "sas_direct")
123                 MAPPED_CHAN=`awk "/^channel / && \\$2 == \"${PCI_ID}\" && \
124                                 \\$3 == ${PORT} { print \\$4; exit }" \
125                                 $CONFIG`
126                 ;;
127         esac
128         printf "%s" ${MAPPED_CHAN}
129 }
130
131 while getopts 'c:s:d:g:mp:h' OPTION; do
132         case ${OPTION} in
133         c)
134                 CONFIG=`readlink -e ${OPTARG}`
135         ;;
136         d)
137         DEV=${OPTARG}
138         ;;
139         g)
140                 TOPOLOGY=$OPTARG
141                 ;;
142         p)
143                 PHYS_PER_PORT=${OPTARG}
144                 ;;
145         m)
146                 MULTIPATH_MODE=yes
147                 ;;
148         s)
149                 SLOT_MAP=`readlink -e ${OPTARG}`
150                 if [ ! -r $SLOT_MAP ] ; then
151                         echo "Error: $SLOT_MAP is nonexistant or unreadable"
152                         exit 1
153                 fi
154                 ;;
155         h)
156                 usage
157                 ;;
158         esac
159 done
160
161 if [ ! -r $CONFIG ] ; then
162         exit 0
163 fi
164
165 if [ -z "$DEV" ] ; then
166         echo "Error: missing required option -d"
167         exit 1
168 fi
169
170 if [ -z "$TOPOLOGY" ] ; then
171         TOPOLOGY=`awk "/^topology /{print \\$2; exit}" $CONFIG`
172 fi
173 TOPOLOGY=${TOPOLOGY:-sas_direct}
174 case $TOPOLOGY in
175         sas_direct|sas_switch)
176                 ;;
177         *)
178                 echo "Error: unknown topology $TOPOLOGY"
179                 exit 1
180                 ;;
181 esac
182
183 if [ -z "$PHYS_PER_PORT" ] ; then
184         PHYS_PER_PORT=`awk "/^phys_per_port /{print \\$2; exit}" $CONFIG`
185 fi
186 PHYS_PER_PORT=${PHYS_PER_PORT:-4}
187 if ! echo $PHYS_PER_PORT | grep -q -E '^[0-9]+$' ; then
188         echo "Error: phys_per_port value $PHYS_PER_PORT is non-numeric"
189         exit 1
190 fi
191
192 if [ -z "$MULTIPATH_MODE" ] ; then
193         MULTIPATH_MODE=`awk "/^multipath /{print \\$2; exit}" $CONFIG`
194 fi
195
196 # Use first running component device if we're handling a dm-mpath device.
197 if [ "$MULTIPATH_MODE" = "yes" ] ; then
198         # If udev didn't tell us the UUID via DM_NAME, find it in /dev/mapper
199         if [ -z "$DM_NAME" ] ; then
200                 DM_NAME=`ls -l --full-time /dev/mapper |
201                          awk "/\/$DEV$/{print \\$9}"`
202         fi
203
204         # For raw disks udev exports DEVTYPE=partition when handling partitions,
205         # and the rules can be written to take advantage of this to append a
206         # -part suffix.  For dm devices we get DEVTYPE=disk even for partitions
207         # so we have to append the -part suffix directly in the helper.
208         if [ "$DEVTYPE" != "partition" ] ; then
209                 PART=`echo $DM_NAME | awk -Fp '/p/{print "-part"$2}'`
210         fi
211
212         # Strip off partition information.
213         DM_NAME=`echo $DM_NAME | sed 's/p[0-9][0-9]*$//'`
214         if [ -z "$DM_NAME" ] ; then
215                 exit 0
216         fi
217
218         # Get the raw scsi device name from multipath -l.
219         DEV=`multipath -l $DM_NAME |awk '/running/{print $3 ; exit}'`
220         if [ -z "$DEV" ] ; then
221                 exit 0
222         fi
223 fi
224
225 if echo $DEV | grep -q ^/devices/ ; then
226         sys_path=$DEV
227 else
228         sys_path=`udevadm info -q path -p /sys/block/$DEV 2>/dev/null`
229 fi
230
231 # Use positional parameters as an ad-hoc array
232 set -- $(echo "$sys_path" | tr / ' ')
233 num_dirs=$#
234 scsi_host_dir="/sys"
235
236 # Get path up to /sys/.../hostX
237 i=1
238 while [ $i -le $num_dirs ] ; do
239         d=$(eval echo \$$i)
240         scsi_host_dir="$scsi_host_dir/$d"
241         echo $d | grep -q -E '^host[0-9]+$' && break
242         i=$(($i + 1))
243 done
244
245 if [ $i = $num_dirs ] ; then
246         exit 0
247 fi
248
249 PCI_ID=$(eval echo \$$(($i -1)) | awk -F: '{print $2":"$3}')
250
251 # In sas_switch mode, the directory four levels beneath /sys/.../hostX
252 # contains symlinks to phy devices that reveal the switch port number.  In
253 # sas_direct mode, the phy links one directory down reveal the HBA port.
254 port_dir=$scsi_host_dir
255 case $TOPOLOGY in
256         "sas_switch") j=$(($i + 4)) ;;
257         "sas_direct") j=$(($i + 1)) ;;
258 esac
259
260 i=$(($i + 1))
261 while [ $i -le $j ] ; do
262         port_dir="$port_dir/$(eval echo \$$i)"
263         i=$(($i + 1))
264 done
265
266 PHY=`ls -d $port_dir/phy* 2>/dev/null | head -1 | awk -F: '{print $NF}'`
267 if [ -z "$PHY" ] ; then
268         exit 0
269 fi
270 PORT=$(( $PHY / $PHYS_PER_PORT ))
271
272 # Look in /sys/.../sas_device/end_device-X for the bay_identifier
273 # attribute.
274 end_device_dir=$port_dir
275 while [ $i -lt $num_dirs ] ; do
276         d=$(eval echo \$$i)
277         end_device_dir="$end_device_dir/$d"
278         if echo $d | grep -q '^end_device' ; then
279                 end_device_dir="$end_device_dir/sas_device/$d"
280                 break
281         fi
282         i=$(($i + 1))
283 done
284
285 SLOT=`cat $end_device_dir/bay_identifier 2>/dev/null`
286 if [ -z "$SLOT" ] ; then
287         exit 0
288 fi
289
290 SLOT=`map_slot $SLOT`
291 CHAN=`map_channel $PCI_ID $PORT`
292 if [ -z "$CHAN" ] ; then
293         exit 0
294 fi
295 ID_VDEV=${CHAN}${SLOT}${PART}
296
297 echo "ID_VDEV=${ID_VDEV}"
298 echo "ID_VDEV_PATH=disk/by-vdev/${ID_VDEV}"