xref: /freebsd/sys/contrib/openzfs/udev/vdev_id (revision b1c1ee4429fcca8f69873a8be66184e68e1b19d7)
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# slot          bay
36#
37# #       PCI_ID  HBA PORT  CHANNEL NAME
38# channel 85:00.0 1         A
39# channel 85:00.0 0         B
40# channel 86:00.0 1         C
41# channel 86:00.0 0         D
42#
43# # Custom mapping for Channel A
44#
45# #    Linux      Mapped
46# #    Slot       Slot      Channel
47# slot 1          7         A
48# slot 2          10        A
49# slot 3          3         A
50# slot 4          6         A
51#
52# # Default mapping for B, C, and D
53# slot 1          4
54# slot 2          2
55# slot 3          1
56# slot 4          3
57
58# #
59# # Example vdev_id.conf - sas_switch
60# #
61#
62# topology      sas_switch
63#
64# #       SWITCH PORT  CHANNEL NAME
65# channel 1            A
66# channel 2            B
67# channel 3            C
68# channel 4            D
69
70# #
71# # Example vdev_id.conf - multipath
72# #
73#
74# multipath yes
75#
76# #       PCI_ID  HBA PORT  CHANNEL NAME
77# channel 85:00.0 1         A
78# channel 85:00.0 0         B
79# channel 86:00.0 1         A
80# channel 86:00.0 0         B
81
82# #
83# # Example vdev_id.conf - multipath / multijbod-daisychaining
84# #
85#
86# multipath yes
87# multijbod yes
88#
89# #       PCI_ID  HBA PORT  CHANNEL NAME
90# channel 85:00.0 1         A
91# channel 85:00.0 0         B
92# channel 86:00.0 1         A
93# channel 86:00.0 0         B
94
95# #
96# # Example vdev_id.conf - multipath / mixed
97# #
98#
99# multipath yes
100# slot mix
101#
102# #       PCI_ID  HBA PORT  CHANNEL NAME
103# channel 85:00.0 3         A
104# channel 85:00.0 2         B
105# channel 86:00.0 3         A
106# channel 86:00.0 2         B
107# channel af:00.0 0         C
108# channel af:00.0 1         C
109
110# #
111# # Example vdev_id.conf - alias
112# #
113#
114# #     by-vdev
115# #     name     fully qualified or base name of device link
116# alias d1       /dev/disk/by-id/wwn-0x5000c5002de3b9ca
117# alias d2       wwn-0x5000c5002def789e
118
119PATH=/bin:/sbin:/usr/bin:/usr/sbin
120CONFIG=/etc/zfs/vdev_id.conf
121PHYS_PER_PORT=
122DEV=
123TOPOLOGY=
124BAY=
125ENCL_ID=""
126UNIQ_ENCL_ID=""
127ZPAD=1
128
129usage() {
130	cat << EOF
131Usage: vdev_id [-h]
132       vdev_id <-d device> [-c config_file] [-p phys_per_port]
133               [-g sas_direct|sas_switch|scsi] [-m]
134
135  -c    specify name of an alternative config file [default=$CONFIG]
136  -d    specify basename of device (i.e. sda)
137  -e    Create enclose device symlinks only (/dev/by-enclosure)
138  -g    Storage network topology [default="$TOPOLOGY"]
139  -m    Run in multipath mode
140  -j    Run in multijbod mode
141  -p    number of phy's per switch port [default=$PHYS_PER_PORT]
142  -h    show this summary
143EOF
144	exit 1
145	# exit with error to avoid processing usage message by a udev rule
146}
147
148map_slot() {
149	LINUX_SLOT=$1
150	CHANNEL=$2
151
152	MAPPED_SLOT=$(awk -v linux_slot="$LINUX_SLOT" -v channel="$CHANNEL" \
153			'$1 == "slot" && $2 == linux_slot && \
154			($4 ~ "^"channel"$" || $4 ~ /^$/) { print $3; exit}' $CONFIG)
155	if [ -z "$MAPPED_SLOT" ] ; then
156		MAPPED_SLOT=$LINUX_SLOT
157	fi
158	printf "%0${ZPAD}d" "${MAPPED_SLOT}"
159}
160
161map_channel() {
162	MAPPED_CHAN=
163	PCI_ID=$1
164	PORT=$2
165
166	case $TOPOLOGY in
167		"sas_switch")
168		MAPPED_CHAN=$(awk -v port="$PORT" \
169			'$1 == "channel" && $2 == port \
170			{ print $3; exit }' $CONFIG)
171		;;
172		"sas_direct"|"scsi")
173		MAPPED_CHAN=$(awk -v pciID="$PCI_ID" -v port="$PORT" \
174			'$1 == "channel" && $2 == pciID && $3 == port \
175			{print $4}' $CONFIG)
176		;;
177	esac
178	printf "%s" "${MAPPED_CHAN}"
179}
180
181get_encl_id() {
182	set -- $(echo $1)
183	count=$#
184
185	i=1
186	while [ $i -le $count ] ; do
187		d=$(eval echo '$'{$i})
188		id=$(cat "/sys/class/enclosure/${d}/id")
189		ENCL_ID="${ENCL_ID} $id"
190		i=$((i + 1))
191	done
192}
193
194get_uniq_encl_id() {
195	for uuid in ${ENCL_ID}; do
196		found=0
197
198		for count in ${UNIQ_ENCL_ID}; do
199			if [ $count = $uuid ]; then
200				found=1
201				break
202			fi
203		done
204
205		if [ $found -eq 0 ]; then
206			UNIQ_ENCL_ID="${UNIQ_ENCL_ID} $uuid"
207		fi
208	done
209}
210
211# map_jbod explainer: The bsg driver knows the difference between a SAS
212# expander and fanout expander. Use hostX instance along with top-level
213# (whole enclosure) expander instances in /sys/class/enclosure and
214# matching a field in an array of expanders, using the index of the
215# matched array field as the enclosure instance, thereby making jbod IDs
216# dynamic. Avoids reliance on high overhead userspace commands like
217# multipath and lsscsi and instead uses existing sysfs data.  $HOSTCHAN
218# variable derived from devpath gymnastics in sas_handler() function.
219map_jbod() {
220	DEVEXP=$(ls -l "/sys/block/$DEV/device/" | grep enclos | awk -F/ '{print $(NF-1) }')
221	DEV=$1
222
223	# Use "set --" to create index values (Arrays)
224	set -- $(ls -l /sys/class/enclosure | grep -v "^total" | awk '{print $9}')
225	# Get count of total elements
226	JBOD_COUNT=$#
227	JBOD_ITEM=$*
228
229	# Build JBODs (enclosure)  id from sys/class/enclosure/<dev>/id
230	get_encl_id "$JBOD_ITEM"
231	# Different expander instances for each paths.
232	# Filter out and keep only unique id.
233	get_uniq_encl_id
234
235	# Identify final 'mapped jbod'
236	j=0
237	for count in ${UNIQ_ENCL_ID}; do
238		i=1
239		j=$((j + 1))
240		while [ $i -le $JBOD_COUNT ] ; do
241			d=$(eval echo '$'{$i})
242			id=$(cat "/sys/class/enclosure/${d}/id")
243			if [ "$d" = "$DEVEXP" ] && [ $id = $count ] ; then
244				MAPPED_JBOD=$j
245				break
246			fi
247			i=$((i + 1))
248		done
249	done
250
251	printf "%d" "${MAPPED_JBOD}"
252}
253
254sas_handler() {
255	if [ -z "$PHYS_PER_PORT" ] ; then
256		PHYS_PER_PORT=$(awk '$1 == "phys_per_port" \
257			{print $2; exit}' $CONFIG)
258	fi
259	PHYS_PER_PORT=${PHYS_PER_PORT:-4}
260
261	if ! echo "$PHYS_PER_PORT" | grep -q -E '^[0-9]+$' ; then
262		echo "Error: phys_per_port value $PHYS_PER_PORT is non-numeric"
263		exit 1
264	fi
265
266	if [ -z "$MULTIPATH_MODE" ] ; then
267		MULTIPATH_MODE=$(awk '$1 == "multipath" \
268			{print $2; exit}' $CONFIG)
269	fi
270
271	if [ -z "$MULTIJBOD_MODE" ] ; then
272		MULTIJBOD_MODE=$(awk '$1 == "multijbod" \
273			{print $2; exit}' $CONFIG)
274	fi
275
276	# Use first running component device if we're handling a dm-mpath device
277	if [ "$MULTIPATH_MODE" = "yes" ] ; then
278		# If udev didn't tell us the UUID via DM_NAME, check /dev/mapper
279		if [ -z "$DM_NAME" ] ; then
280			DM_NAME=$(ls -l --full-time /dev/mapper |
281				grep "$DEV"$ | awk '{print $9}')
282		fi
283
284		# For raw disks udev exports DEVTYPE=partition when
285		# handling partitions, and the rules can be written to
286		# take advantage of this to append a -part suffix.  For
287		# dm devices we get DEVTYPE=disk even for partitions so
288		# we have to append the -part suffix directly in the
289		# helper.
290		if [ "$DEVTYPE" != "partition" ] ; then
291                        # WWNs end with number -> p<partition>, alphabet -> <partition>
292                        # '3' + (WWN length 16) = 17
293                        PART=${DM_NAME:17}
294                        if [[ $PART = "p"*  ]]; then
295                                # Match p[number], remove the 'p' and prepend "-part"
296                                PART=$(echo "$DM_NAME" |
297                                    awk 'match($0,/p[0-9]+$/) {print "-part"substr($0,RSTART+1,RLENGTH-1)}')
298                        elif [[ $PART != "" ]]; then
299                                PART="-part"${PART}
300                        fi
301		fi
302
303		# Strip off partition information.
304                DM_NAME=${DM_NAME:0:17}
305		if [ -z "$DM_NAME" ] ; then
306			return
307		fi
308
309		# Utilize DM device name to gather subordinate block devices
310		# using sysfs to avoid userspace utilities
311
312		# If our DEVNAME is something like /dev/dm-177, then we may be
313		# able to get our DMDEV from it.
314		DMDEV=$(echo $DEVNAME | sed 's;/dev/;;g')
315                if [ -n "$DMDEV"  ]; then
316                        DEV=$(ls /sys/block/$DMDEV/slaves)
317                        for elm in "${DEV[@]}"; do
318                                if [[ $elm == "dm-"* ]]; then
319                                        DMDEV=$elm
320                                        break
321                                fi
322                        done
323                fi
324
325		if [ ! -e /sys/block/$DMDEV/slaves/* ] ; then
326			# It's not there, try looking in /dev/mapper
327			DMDEV=$(ls -l --full-time /dev/mapper | grep $DM_NAME |
328			awk '{gsub("../", " "); print $NF}' | head -n 1)
329		fi
330
331		# Use sysfs pointers in /sys/block/dm-X/slaves because using
332		# userspace tools creates lots of overhead and should be avoided
333		# whenever possible. Use awk to isolate lowest instance of
334		# sd device member in dm device group regardless of string
335		# length.
336		DEV=$(ls "/sys/block/$DMDEV/slaves" | awk '
337			{ len=sprintf ("%20s",length($0)); gsub(/ /,0,str); a[NR]=len "_" $0; }
338			END {
339				asort(a)
340				print substr(a[1],22)
341			}')
342
343		if [ -z "$DEV" ] ; then
344			return
345		fi
346	fi
347
348	if echo "$DEV" | grep -q ^/devices/ ; then
349		sys_path=$DEV
350	else
351		sys_path=$(udevadm info -q path -p "/sys/block/$DEV" 2>/dev/null)
352	fi
353
354	# Use positional parameters as an ad-hoc array
355	set -- $(echo "$sys_path" | tr / ' ')
356	num_dirs=$#
357	scsi_host_dir="/sys"
358
359	# Get path up to /sys/.../hostX
360	i=1
361
362	while [ $i -le "$num_dirs" ] ; do
363		d=$(eval echo '$'{$i})
364		scsi_host_dir="$scsi_host_dir/$d"
365		echo "$d" | grep -q -E '^host[0-9]+$' && break
366		i=$((i + 1))
367	done
368
369	# Lets grab the SAS host channel number and save it for JBOD sorting later
370	HOSTCHAN=$(echo "$d" | awk -F/ '{ gsub("host","",$NF); print $NF}')
371
372	if [ $i = "$num_dirs" ] ; then
373		return
374	fi
375
376	PCI_ID=$(eval echo '$'{$((i -1))} | awk -F: '{print $2":"$3}')
377
378	# In sas_switch mode, the directory four levels beneath
379	# /sys/.../hostX contains symlinks to phy devices that reveal
380	# the switch port number.  In sas_direct mode, the phy links one
381	# directory down reveal the HBA port.
382	port_dir=$scsi_host_dir
383
384	case $TOPOLOGY in
385		"sas_switch") j=$((i + 4)) ;;
386		"sas_direct") j=$((i + 1)) ;;
387	esac
388
389	i=$((i + 1))
390
391	while [ $i -le $j ] ; do
392		port_dir="$port_dir/$(eval echo '$'{$i})"
393		i=$((i + 1))
394	done
395
396	PHY=$(ls -vd "$port_dir"/phy* 2>/dev/null | head -1 | awk -F: '{print $NF}')
397	if [ -z "$PHY" ] ; then
398		PHY=0
399	fi
400	PORT=$((PHY / PHYS_PER_PORT))
401
402	# Look in /sys/.../sas_device/end_device-X for the bay_identifier
403	# attribute.
404	end_device_dir=$port_dir
405
406	while [ $i -lt "$num_dirs" ] ; do
407		d=$(eval echo '$'{$i})
408		end_device_dir="$end_device_dir/$d"
409		if echo "$d" | grep -q '^end_device' ; then
410			end_device_dir="$end_device_dir/sas_device/$d"
411			break
412		fi
413		i=$((i + 1))
414	done
415
416	# Add 'mix' slot type for environments where dm-multipath devices
417	# include end-devices connected via SAS expanders or direct connection
418	# to SAS HBA. A mixed connectivity environment such as pool devices
419	# contained in a SAS JBOD and spare drives or log devices directly
420	# connected in a server backplane without expanders in the I/O path.
421	SLOT=
422
423	case $BAY in
424	"bay")
425		SLOT=$(cat "$end_device_dir/bay_identifier" 2>/dev/null)
426		;;
427	"mix")
428		if [ $(cat "$end_device_dir/bay_identifier" 2>/dev/null) ] ; then
429			SLOT=$(cat "$end_device_dir/bay_identifier" 2>/dev/null)
430		else
431			SLOT=$(cat "$end_device_dir/phy_identifier" 2>/dev/null)
432		fi
433		;;
434	"phy")
435		SLOT=$(cat "$end_device_dir/phy_identifier" 2>/dev/null)
436		;;
437	"port")
438		d=$(eval echo '$'{$i})
439		SLOT=$(echo "$d" | sed -e 's/^.*://')
440		;;
441	"id")
442		i=$((i + 1))
443		d=$(eval echo '$'{$i})
444		SLOT=$(echo "$d" | sed -e 's/^.*://')
445		;;
446	"lun")
447		i=$((i + 2))
448		d=$(eval echo '$'{$i})
449		SLOT=$(echo "$d" | sed -e 's/^.*://')
450		;;
451	"bay_lun")
452		# Like 'bay' but with the LUN number appened. Added for SAS
453		# multi-actuator HDDs, where one physical drive has multiple
454		# LUNs, thus multiple logical drives share the same bay number
455		i=$((i + 2))
456		d=$(eval echo '$'{$i})
457		LUN="-lun$(echo "$d" | sed -e 's/^.*://')"
458		SLOT=$(cat "$end_device_dir/bay_identifier" 2>/dev/null)
459		;;
460	"ses")
461		# look for this SAS path in all SCSI Enclosure Services
462		# (SES) enclosures
463		sas_address=$(cat "$end_device_dir/sas_address" 2>/dev/null)
464		enclosures=$(lsscsi -g | \
465			sed -n -e '/enclosu/s/^.* \([^ ][^ ]*\) *$/\1/p')
466		for enclosure in $enclosures; do
467			set -- $(sg_ses -p aes "$enclosure" | \
468				awk "/device slot number:/{slot=\$12} \
469					/SAS address: $sas_address/\
470					{print slot}")
471			SLOT=$1
472			if [ -n "$SLOT" ] ; then
473				break
474			fi
475		done
476		;;
477	esac
478	if [ -z "$SLOT" ] ; then
479		return
480	fi
481
482	if [ "$MULTIJBOD_MODE" = "yes" ] ; then
483		CHAN=$(map_channel "$PCI_ID" "$PORT")
484		SLOT=$(map_slot "$SLOT" "$CHAN")
485		JBOD=$(map_jbod "$DEV")
486
487		if [ -z "$CHAN" ] ; then
488			return
489		fi
490		echo "${CHAN}"-"${JBOD}"-"${SLOT}${LUN}${PART}"
491	else
492		CHAN=$(map_channel "$PCI_ID" "$PORT")
493		SLOT=$(map_slot "$SLOT" "$CHAN")
494
495		if [ -z "$CHAN" ] ; then
496			return
497		fi
498		echo "${CHAN}${SLOT}${LUN}${PART}"
499	fi
500}
501
502scsi_handler() {
503	if [ -z "$FIRST_BAY_NUMBER" ] ; then
504		FIRST_BAY_NUMBER=$(awk '$1 == "first_bay_number" \
505			{print $2; exit}' $CONFIG)
506	fi
507	FIRST_BAY_NUMBER=${FIRST_BAY_NUMBER:-0}
508
509	if [ -z "$PHYS_PER_PORT" ] ; then
510		PHYS_PER_PORT=$(awk '$1 == "phys_per_port" \
511			{print $2; exit}' $CONFIG)
512	fi
513	PHYS_PER_PORT=${PHYS_PER_PORT:-4}
514
515	if ! echo "$PHYS_PER_PORT" | grep -q -E '^[0-9]+$' ; then
516		echo "Error: phys_per_port value $PHYS_PER_PORT is non-numeric"
517		exit 1
518	fi
519
520	if [ -z "$MULTIPATH_MODE" ] ; then
521		MULTIPATH_MODE=$(awk '$1 == "multipath" \
522			{print $2; exit}' $CONFIG)
523	fi
524
525	# Use first running component device if we're handling a dm-mpath device
526	if [ "$MULTIPATH_MODE" = "yes" ] ; then
527		# If udev didn't tell us the UUID via DM_NAME, check /dev/mapper
528		if [ -z "$DM_NAME" ] ; then
529			DM_NAME=$(ls -l --full-time /dev/mapper |
530				grep "$DEV"$ | awk '{print $9}')
531		fi
532
533		# For raw disks udev exports DEVTYPE=partition when
534		# handling partitions, and the rules can be written to
535		# take advantage of this to append a -part suffix.  For
536		# dm devices we get DEVTYPE=disk even for partitions so
537		# we have to append the -part suffix directly in the
538		# helper.
539		if [ "$DEVTYPE" != "partition" ] ; then
540                        # WWNs end with number -> p<partition>, alphabet -> <partition>
541                        # '3' + (WWN length 16) = 17
542                        PART=${DM_NAME:17}
543                        if [[ $PART = "p"*  ]]; then
544                                # Match p[number], remove the 'p' and prepend "-part"
545                                PART=$(echo "$DM_NAME" |
546                                    awk 'match($0,/p[0-9]+$/) {print "-part"substr($0,RSTART+1,RLENGTH-1)}')
547                        elif [[ $PART != "" ]]; then
548                                PART="-part"${PART}
549                        fi
550		fi
551
552		# Strip off partition information.
553                DM_NAME=${DM_NAME:0:17}
554		if [ -z "$DM_NAME" ] ; then
555			return
556		fi
557
558		# Get the raw scsi device name from multipath -ll. Strip off
559		# leading pipe symbols to make field numbering consistent.
560		DEV=$(multipath -ll "$DM_NAME" |
561			awk '/running/{gsub("^[|]"," "); print $3 ; exit}')
562		if [ -z "$DEV" ] ; then
563			return
564		fi
565	fi
566
567	if echo "$DEV" | grep -q ^/devices/ ; then
568		sys_path=$DEV
569	else
570		sys_path=$(udevadm info -q path -p "/sys/block/$DEV" 2>/dev/null)
571	fi
572
573	# expect sys_path like this, for example:
574	# /devices/pci0000:00/0000:00:0b.0/0000:09:00.0/0000:0a:05.0/0000:0c:00.0/host3/target3:1:0/3:1:0:21/block/sdv
575
576	# Use positional parameters as an ad-hoc array
577	set -- $(echo "$sys_path" | tr / ' ')
578	num_dirs=$#
579	scsi_host_dir="/sys"
580
581	# Get path up to /sys/.../hostX
582	i=1
583
584	while [ $i -le "$num_dirs" ] ; do
585		d=$(eval echo '$'{$i})
586		scsi_host_dir="$scsi_host_dir/$d"
587
588		echo "$d" | grep -q -E '^host[0-9]+$' && break
589		i=$((i + 1))
590	done
591
592	if [ $i = "$num_dirs" ] ; then
593		return
594	fi
595
596	PCI_ID=$(eval echo '$'{$((i -1))} | awk -F: '{print $2":"$3}')
597
598	# In scsi mode, the directory two levels beneath
599	# /sys/.../hostX reveals the port and slot.
600	port_dir=$scsi_host_dir
601	j=$((i + 2))
602
603	i=$((i + 1))
604	while [ $i -le $j ] ; do
605		port_dir="$port_dir/$(eval echo '$'{$i})"
606		i=$((i + 1))
607	done
608
609	set -- $(echo "$port_dir" | sed -e 's/^.*:\([^:]*\):\([^:]*\)$/\1 \2/')
610	PORT=$1
611	SLOT=$(($2 + FIRST_BAY_NUMBER))
612
613	if [ -z "$SLOT" ] ; then
614		return
615	fi
616
617	CHAN=$(map_channel "$PCI_ID" "$PORT")
618	SLOT=$(map_slot "$SLOT" "$CHAN")
619
620	if [ -z "$CHAN" ] ; then
621		return
622	fi
623	echo "${CHAN}${SLOT}${PART}"
624}
625
626# Figure out the name for the enclosure symlink
627enclosure_handler () {
628	# We get all the info we need from udev's DEVPATH variable:
629	#
630	# DEVPATH=/sys/devices/pci0000:00/0000:00:03.0/0000:05:00.0/host0/subsystem/devices/0:0:0:0/scsi_generic/sg0
631
632	# Get the enclosure ID ("0:0:0:0")
633	ENC="${DEVPATH%/*}"
634	ENC="${ENC%/*}"
635	ENC="${ENC##*/}"
636	if [ ! -d "/sys/class/enclosure/$ENC" ] ; then
637		# Not an enclosure, bail out
638		return
639	fi
640
641	# Get the long sysfs device path to our enclosure. Looks like:
642	# /devices/pci0000:00/0000:00:03.0/0000:05:00.0/host0/port-0:0/ ... /enclosure/0:0:0:0
643
644	ENC_DEVICE=$(readlink "/sys/class/enclosure/$ENC")
645
646	# Grab the full path to the hosts port dir:
647	# /devices/pci0000:00/0000:00:03.0/0000:05:00.0/host0/port-0:0
648	PORT_DIR=$(echo "$ENC_DEVICE" | grep -Eo '.+host[0-9]+/port-[0-9]+:[0-9]+')
649
650	# Get the port number
651	PORT_ID=$(echo "$PORT_DIR" | grep -Eo "[0-9]+$")
652
653	# The PCI directory is two directories up from the port directory
654	# /sys/devices/pci0000:00/0000:00:03.0/0000:05:00.0
655	PCI_ID_LONG="$(readlink -m "/sys/$PORT_DIR/../..")"
656	PCI_ID_LONG="${PCI_ID_LONG##*/}"
657
658	# Strip down the PCI address from 0000:05:00.0 to 05:00.0
659	PCI_ID="${PCI_ID_LONG#[0-9]*:}"
660
661	# Name our device according to vdev_id.conf (like "L0" or "U1").
662	NAME=$(awk "/channel/{if (\$1 == \"channel\" && \$2 == \"$PCI_ID\" && \
663		\$3 == \"$PORT_ID\") {print \$4\$3}}" $CONFIG)
664
665	echo "${NAME}"
666}
667
668alias_handler () {
669	# Special handling is needed to correctly append a -part suffix
670	# to partitions of device mapper devices.  The DEVTYPE attribute
671	# is normally set to "disk" instead of "partition" in this case,
672	# so the udev rules won't handle that for us as they do for
673	# "plain" block devices.
674	#
675	# For example, we may have the following links for a device and its
676	# partitions,
677	#
678	#  /dev/disk/by-id/dm-name-isw_dibgbfcije_ARRAY0   -> ../../dm-0
679	#  /dev/disk/by-id/dm-name-isw_dibgbfcije_ARRAY0p1 -> ../../dm-1
680	#  /dev/disk/by-id/dm-name-isw_dibgbfcije_ARRAY0p2 -> ../../dm-3
681	#
682	# and the following alias in vdev_id.conf.
683	#
684	#   alias A0 dm-name-isw_dibgbfcije_ARRAY0
685	#
686	# The desired outcome is for the following links to be created
687	# without having explicitly defined aliases for the partitions.
688	#
689	#  /dev/disk/by-vdev/A0       -> ../../dm-0
690	#  /dev/disk/by-vdev/A0-part1 -> ../../dm-1
691	#  /dev/disk/by-vdev/A0-part2 -> ../../dm-3
692	#
693	# Warning: The following grep pattern will misidentify whole-disk
694	#          devices whose names end with 'p' followed by a string of
695	#          digits as partitions, causing alias creation to fail. This
696	#          ambiguity seems unavoidable, so devices using this facility
697	#          must not use such names.
698	DM_PART=
699	if echo "$DM_NAME" | grep -q -E 'p[0-9][0-9]*$' ; then
700		if [ "$DEVTYPE" != "partition" ] ; then
701			# Match p[number], remove the 'p' and prepend "-part"
702			DM_PART=$(echo "$DM_NAME" |
703			    awk 'match($0,/p[0-9]+$/) {print "-part"substr($0,RSTART+1,RLENGTH-1)}')
704		fi
705	fi
706
707	# DEVLINKS attribute must have been populated by already-run udev rules.
708	for link in $DEVLINKS ; do
709		# Remove partition information to match key of top-level device.
710		if [ -n "$DM_PART" ] ; then
711			link=$(echo "$link" | sed 's/p[0-9][0-9]*$//')
712		fi
713		# Check both the fully qualified and the base name of link.
714		for l in $link ${link##*/} ; do
715			if [ ! -z "$l" ]; then
716				alias=$(awk -v var="$l" '($1 == "alias") && \
717					($3 == var) \
718					{ print $2; exit }' $CONFIG)
719				if [ -n "$alias" ] ; then
720					echo "${alias}${DM_PART}"
721					return
722				fi
723			fi
724		done
725	done
726}
727
728# main
729while getopts 'c:d:eg:jmp:h' OPTION; do
730	case ${OPTION} in
731	c)
732		CONFIG=${OPTARG}
733		;;
734	d)
735		DEV=${OPTARG}
736		;;
737	e)
738	# When udev sees a scsi_generic device, it calls this script with -e to
739	# create the enclosure device symlinks only.  We also need
740	# "enclosure_symlinks yes" set in vdev_id.config to actually create the
741	# symlink.
742	ENCLOSURE_MODE=$(awk '{if ($1 == "enclosure_symlinks") \
743		print $2}' "$CONFIG")
744
745	if [ "$ENCLOSURE_MODE" != "yes" ] ; then
746		exit 0
747	fi
748		;;
749	g)
750		TOPOLOGY=$OPTARG
751		;;
752	p)
753		PHYS_PER_PORT=${OPTARG}
754		;;
755	j)
756		MULTIJBOD_MODE=yes
757		;;
758	m)
759		MULTIPATH_MODE=yes
760		;;
761	h)
762		usage
763		;;
764	esac
765done
766
767if [ ! -r "$CONFIG" ] ; then
768	echo "Error: Config file \"$CONFIG\" not found"
769	exit 1
770fi
771
772if [ -z "$DEV" ] && [ -z "$ENCLOSURE_MODE" ] ; then
773	echo "Error: missing required option -d"
774	exit 1
775fi
776
777if [ -z "$TOPOLOGY" ] ; then
778	TOPOLOGY=$(awk '($1 == "topology") {print $2; exit}' "$CONFIG")
779fi
780
781if [ -z "$BAY" ] ; then
782	BAY=$(awk '($1 == "slot") {print $2; exit}' "$CONFIG")
783fi
784
785ZPAD=$(awk '($1 == "zpad_slot") {print $2; exit}' "$CONFIG")
786
787TOPOLOGY=${TOPOLOGY:-sas_direct}
788
789# Should we create /dev/by-enclosure symlinks?
790if [ "$ENCLOSURE_MODE" = "yes" ] && [ "$TOPOLOGY" = "sas_direct" ] ; then
791	ID_ENCLOSURE=$(enclosure_handler)
792	if [ -z "$ID_ENCLOSURE" ] ; then
793		exit 0
794	fi
795
796	# Just create the symlinks to the enclosure devices and then exit.
797	ENCLOSURE_PREFIX=$(awk '/enclosure_symlinks_prefix/{print $2}' "$CONFIG")
798	if [ -z "$ENCLOSURE_PREFIX" ] ; then
799		ENCLOSURE_PREFIX="enc"
800	fi
801	echo "ID_ENCLOSURE=$ID_ENCLOSURE"
802	echo "ID_ENCLOSURE_PATH=by-enclosure/$ENCLOSURE_PREFIX-$ID_ENCLOSURE"
803	exit 0
804fi
805
806# First check if an alias was defined for this device.
807ID_VDEV=$(alias_handler)
808
809if [ -z "$ID_VDEV" ] ; then
810	BAY=${BAY:-bay}
811	case $TOPOLOGY in
812		sas_direct|sas_switch)
813			ID_VDEV=$(sas_handler)
814			;;
815		scsi)
816			ID_VDEV=$(scsi_handler)
817			;;
818		*)
819			echo "Error: unknown topology $TOPOLOGY"
820			exit 1
821			;;
822	esac
823fi
824
825if [ -n "$ID_VDEV" ] ; then
826	echo "ID_VDEV=${ID_VDEV}"
827	echo "ID_VDEV_PATH=disk/by-vdev/${ID_VDEV}"
828fi
829