xref: /freebsd/sys/contrib/openzfs/udev/vdev_id (revision 87bf66d4a7488c496af110d4d05cc0273d49f82e)
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			# Match p[number], remove the 'p' and prepend "-part"
292			PART=$(echo "$DM_NAME" |
293				awk 'match($0,/p[0-9]+$/) {print "-part"substr($0,RSTART+1,RLENGTH-1)}')
294		fi
295
296		# Strip off partition information.
297		DM_NAME=$(echo "$DM_NAME" | sed 's/p[0-9][0-9]*$//')
298		if [ -z "$DM_NAME" ] ; then
299			return
300		fi
301
302		# Utilize DM device name to gather subordinate block devices
303		# using sysfs to avoid userspace utilities
304
305		# If our DEVNAME is something like /dev/dm-177, then we may be
306		# able to get our DMDEV from it.
307		DMDEV=$(echo $DEVNAME | sed 's;/dev/;;g')
308		if [ ! -e /sys/block/$DMDEV/slaves/* ] ; then
309			# It's not there, try looking in /dev/mapper
310			DMDEV=$(ls -l --full-time /dev/mapper | grep $DM_NAME |
311			awk '{gsub("../", " "); print $NF}')
312		fi
313
314		# Use sysfs pointers in /sys/block/dm-X/slaves because using
315		# userspace tools creates lots of overhead and should be avoided
316		# whenever possible. Use awk to isolate lowest instance of
317		# sd device member in dm device group regardless of string
318		# length.
319		DEV=$(ls "/sys/block/$DMDEV/slaves" | awk '
320			{ len=sprintf ("%20s",length($0)); gsub(/ /,0,str); a[NR]=len "_" $0; }
321			END {
322				asort(a)
323				print substr(a[1],22)
324			}')
325
326		if [ -z "$DEV" ] ; then
327			return
328		fi
329	fi
330
331	if echo "$DEV" | grep -q ^/devices/ ; then
332		sys_path=$DEV
333	else
334		sys_path=$(udevadm info -q path -p "/sys/block/$DEV" 2>/dev/null)
335	fi
336
337	# Use positional parameters as an ad-hoc array
338	set -- $(echo "$sys_path" | tr / ' ')
339	num_dirs=$#
340	scsi_host_dir="/sys"
341
342	# Get path up to /sys/.../hostX
343	i=1
344
345	while [ $i -le "$num_dirs" ] ; do
346		d=$(eval echo '$'{$i})
347		scsi_host_dir="$scsi_host_dir/$d"
348		echo "$d" | grep -q -E '^host[0-9]+$' && break
349		i=$((i + 1))
350	done
351
352	# Lets grab the SAS host channel number and save it for JBOD sorting later
353	HOSTCHAN=$(echo "$d" | awk -F/ '{ gsub("host","",$NF); print $NF}')
354
355	if [ $i = "$num_dirs" ] ; then
356		return
357	fi
358
359	PCI_ID=$(eval echo '$'{$((i -1))} | awk -F: '{print $2":"$3}')
360
361	# In sas_switch mode, the directory four levels beneath
362	# /sys/.../hostX contains symlinks to phy devices that reveal
363	# the switch port number.  In sas_direct mode, the phy links one
364	# directory down reveal the HBA port.
365	port_dir=$scsi_host_dir
366
367	case $TOPOLOGY in
368		"sas_switch") j=$((i + 4)) ;;
369		"sas_direct") j=$((i + 1)) ;;
370	esac
371
372	i=$((i + 1))
373
374	while [ $i -le $j ] ; do
375		port_dir="$port_dir/$(eval echo '$'{$i})"
376		i=$((i + 1))
377	done
378
379	PHY=$(ls -vd "$port_dir"/phy* 2>/dev/null | head -1 | awk -F: '{print $NF}')
380	if [ -z "$PHY" ] ; then
381		PHY=0
382	fi
383	PORT=$((PHY / PHYS_PER_PORT))
384
385	# Look in /sys/.../sas_device/end_device-X for the bay_identifier
386	# attribute.
387	end_device_dir=$port_dir
388
389	while [ $i -lt "$num_dirs" ] ; do
390		d=$(eval echo '$'{$i})
391		end_device_dir="$end_device_dir/$d"
392		if echo "$d" | grep -q '^end_device' ; then
393			end_device_dir="$end_device_dir/sas_device/$d"
394			break
395		fi
396		i=$((i + 1))
397	done
398
399	# Add 'mix' slot type for environments where dm-multipath devices
400	# include end-devices connected via SAS expanders or direct connection
401	# to SAS HBA. A mixed connectivity environment such as pool devices
402	# contained in a SAS JBOD and spare drives or log devices directly
403	# connected in a server backplane without expanders in the I/O path.
404	SLOT=
405
406	case $BAY in
407	"bay")
408		SLOT=$(cat "$end_device_dir/bay_identifier" 2>/dev/null)
409		;;
410	"mix")
411		if [ $(cat "$end_device_dir/bay_identifier" 2>/dev/null) ] ; then
412			SLOT=$(cat "$end_device_dir/bay_identifier" 2>/dev/null)
413		else
414			SLOT=$(cat "$end_device_dir/phy_identifier" 2>/dev/null)
415		fi
416		;;
417	"phy")
418		SLOT=$(cat "$end_device_dir/phy_identifier" 2>/dev/null)
419		;;
420	"port")
421		d=$(eval echo '$'{$i})
422		SLOT=$(echo "$d" | sed -e 's/^.*://')
423		;;
424	"id")
425		i=$((i + 1))
426		d=$(eval echo '$'{$i})
427		SLOT=$(echo "$d" | sed -e 's/^.*://')
428		;;
429	"lun")
430		i=$((i + 2))
431		d=$(eval echo '$'{$i})
432		SLOT=$(echo "$d" | sed -e 's/^.*://')
433		;;
434	"bay_lun")
435		# Like 'bay' but with the LUN number appened. Added for SAS
436		# multi-actuator HDDs, where one physical drive has multiple
437		# LUNs, thus multiple logical drives share the same bay number
438		i=$((i + 2))
439		d=$(eval echo '$'{$i})
440		LUN="-lun$(echo "$d" | sed -e 's/^.*://')"
441		SLOT=$(cat "$end_device_dir/bay_identifier" 2>/dev/null)
442		;;
443	"ses")
444		# look for this SAS path in all SCSI Enclosure Services
445		# (SES) enclosures
446		sas_address=$(cat "$end_device_dir/sas_address" 2>/dev/null)
447		enclosures=$(lsscsi -g | \
448			sed -n -e '/enclosu/s/^.* \([^ ][^ ]*\) *$/\1/p')
449		for enclosure in $enclosures; do
450			set -- $(sg_ses -p aes "$enclosure" | \
451				awk "/device slot number:/{slot=\$12} \
452					/SAS address: $sas_address/\
453					{print slot}")
454			SLOT=$1
455			if [ -n "$SLOT" ] ; then
456				break
457			fi
458		done
459		;;
460	esac
461	if [ -z "$SLOT" ] ; then
462		return
463	fi
464
465	if [ "$MULTIJBOD_MODE" = "yes" ] ; then
466		CHAN=$(map_channel "$PCI_ID" "$PORT")
467		SLOT=$(map_slot "$SLOT" "$CHAN")
468		JBOD=$(map_jbod "$DEV")
469
470		if [ -z "$CHAN" ] ; then
471			return
472		fi
473		echo "${CHAN}"-"${JBOD}"-"${SLOT}${LUN}${PART}"
474	else
475		CHAN=$(map_channel "$PCI_ID" "$PORT")
476		SLOT=$(map_slot "$SLOT" "$CHAN")
477
478		if [ -z "$CHAN" ] ; then
479			return
480		fi
481		echo "${CHAN}${SLOT}${LUN}${PART}"
482	fi
483}
484
485scsi_handler() {
486	if [ -z "$FIRST_BAY_NUMBER" ] ; then
487		FIRST_BAY_NUMBER=$(awk '$1 == "first_bay_number" \
488			{print $2; exit}' $CONFIG)
489	fi
490	FIRST_BAY_NUMBER=${FIRST_BAY_NUMBER:-0}
491
492	if [ -z "$PHYS_PER_PORT" ] ; then
493		PHYS_PER_PORT=$(awk '$1 == "phys_per_port" \
494			{print $2; exit}' $CONFIG)
495	fi
496	PHYS_PER_PORT=${PHYS_PER_PORT:-4}
497
498	if ! echo "$PHYS_PER_PORT" | grep -q -E '^[0-9]+$' ; then
499		echo "Error: phys_per_port value $PHYS_PER_PORT is non-numeric"
500		exit 1
501	fi
502
503	if [ -z "$MULTIPATH_MODE" ] ; then
504		MULTIPATH_MODE=$(awk '$1 == "multipath" \
505			{print $2; exit}' $CONFIG)
506	fi
507
508	# Use first running component device if we're handling a dm-mpath device
509	if [ "$MULTIPATH_MODE" = "yes" ] ; then
510		# If udev didn't tell us the UUID via DM_NAME, check /dev/mapper
511		if [ -z "$DM_NAME" ] ; then
512			DM_NAME=$(ls -l --full-time /dev/mapper |
513				grep "$DEV"$ | awk '{print $9}')
514		fi
515
516		# For raw disks udev exports DEVTYPE=partition when
517		# handling partitions, and the rules can be written to
518		# take advantage of this to append a -part suffix.  For
519		# dm devices we get DEVTYPE=disk even for partitions so
520		# we have to append the -part suffix directly in the
521		# helper.
522		if [ "$DEVTYPE" != "partition" ] ; then
523			# Match p[number], remove the 'p' and prepend "-part"
524			PART=$(echo "$DM_NAME" |
525			    awk 'match($0,/p[0-9]+$/) {print "-part"substr($0,RSTART+1,RLENGTH-1)}')
526		fi
527
528		# Strip off partition information.
529		DM_NAME=$(echo "$DM_NAME" | sed 's/p[0-9][0-9]*$//')
530		if [ -z "$DM_NAME" ] ; then
531			return
532		fi
533
534		# Get the raw scsi device name from multipath -ll. Strip off
535		# leading pipe symbols to make field numbering consistent.
536		DEV=$(multipath -ll "$DM_NAME" |
537			awk '/running/{gsub("^[|]"," "); print $3 ; exit}')
538		if [ -z "$DEV" ] ; then
539			return
540		fi
541	fi
542
543	if echo "$DEV" | grep -q ^/devices/ ; then
544		sys_path=$DEV
545	else
546		sys_path=$(udevadm info -q path -p "/sys/block/$DEV" 2>/dev/null)
547	fi
548
549	# expect sys_path like this, for example:
550	# /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
551
552	# Use positional parameters as an ad-hoc array
553	set -- $(echo "$sys_path" | tr / ' ')
554	num_dirs=$#
555	scsi_host_dir="/sys"
556
557	# Get path up to /sys/.../hostX
558	i=1
559
560	while [ $i -le "$num_dirs" ] ; do
561		d=$(eval echo '$'{$i})
562		scsi_host_dir="$scsi_host_dir/$d"
563
564		echo "$d" | grep -q -E '^host[0-9]+$' && break
565		i=$((i + 1))
566	done
567
568	if [ $i = "$num_dirs" ] ; then
569		return
570	fi
571
572	PCI_ID=$(eval echo '$'{$((i -1))} | awk -F: '{print $2":"$3}')
573
574	# In scsi mode, the directory two levels beneath
575	# /sys/.../hostX reveals the port and slot.
576	port_dir=$scsi_host_dir
577	j=$((i + 2))
578
579	i=$((i + 1))
580	while [ $i -le $j ] ; do
581		port_dir="$port_dir/$(eval echo '$'{$i})"
582		i=$((i + 1))
583	done
584
585	set -- $(echo "$port_dir" | sed -e 's/^.*:\([^:]*\):\([^:]*\)$/\1 \2/')
586	PORT=$1
587	SLOT=$(($2 + FIRST_BAY_NUMBER))
588
589	if [ -z "$SLOT" ] ; then
590		return
591	fi
592
593	CHAN=$(map_channel "$PCI_ID" "$PORT")
594	SLOT=$(map_slot "$SLOT" "$CHAN")
595
596	if [ -z "$CHAN" ] ; then
597		return
598	fi
599	echo "${CHAN}${SLOT}${PART}"
600}
601
602# Figure out the name for the enclosure symlink
603enclosure_handler () {
604	# We get all the info we need from udev's DEVPATH variable:
605	#
606	# DEVPATH=/sys/devices/pci0000:00/0000:00:03.0/0000:05:00.0/host0/subsystem/devices/0:0:0:0/scsi_generic/sg0
607
608	# Get the enclosure ID ("0:0:0:0")
609	ENC="${DEVPATH%/*}"
610	ENC="${ENC%/*}"
611	ENC="${ENC##*/}"
612	if [ ! -d "/sys/class/enclosure/$ENC" ] ; then
613		# Not an enclosure, bail out
614		return
615	fi
616
617	# Get the long sysfs device path to our enclosure. Looks like:
618	# /devices/pci0000:00/0000:00:03.0/0000:05:00.0/host0/port-0:0/ ... /enclosure/0:0:0:0
619
620	ENC_DEVICE=$(readlink "/sys/class/enclosure/$ENC")
621
622	# Grab the full path to the hosts port dir:
623	# /devices/pci0000:00/0000:00:03.0/0000:05:00.0/host0/port-0:0
624	PORT_DIR=$(echo "$ENC_DEVICE" | grep -Eo '.+host[0-9]+/port-[0-9]+:[0-9]+')
625
626	# Get the port number
627	PORT_ID=$(echo "$PORT_DIR" | grep -Eo "[0-9]+$")
628
629	# The PCI directory is two directories up from the port directory
630	# /sys/devices/pci0000:00/0000:00:03.0/0000:05:00.0
631	PCI_ID_LONG="$(readlink -m "/sys/$PORT_DIR/../..")"
632	PCI_ID_LONG="${PCI_ID_LONG##*/}"
633
634	# Strip down the PCI address from 0000:05:00.0 to 05:00.0
635	PCI_ID="${PCI_ID_LONG#[0-9]*:}"
636
637	# Name our device according to vdev_id.conf (like "L0" or "U1").
638	NAME=$(awk "/channel/{if (\$1 == \"channel\" && \$2 == \"$PCI_ID\" && \
639		\$3 == \"$PORT_ID\") {print \$4\$3}}" $CONFIG)
640
641	echo "${NAME}"
642}
643
644alias_handler () {
645	# Special handling is needed to correctly append a -part suffix
646	# to partitions of device mapper devices.  The DEVTYPE attribute
647	# is normally set to "disk" instead of "partition" in this case,
648	# so the udev rules won't handle that for us as they do for
649	# "plain" block devices.
650	#
651	# For example, we may have the following links for a device and its
652	# partitions,
653	#
654	#  /dev/disk/by-id/dm-name-isw_dibgbfcije_ARRAY0   -> ../../dm-0
655	#  /dev/disk/by-id/dm-name-isw_dibgbfcije_ARRAY0p1 -> ../../dm-1
656	#  /dev/disk/by-id/dm-name-isw_dibgbfcije_ARRAY0p2 -> ../../dm-3
657	#
658	# and the following alias in vdev_id.conf.
659	#
660	#   alias A0 dm-name-isw_dibgbfcije_ARRAY0
661	#
662	# The desired outcome is for the following links to be created
663	# without having explicitly defined aliases for the partitions.
664	#
665	#  /dev/disk/by-vdev/A0       -> ../../dm-0
666	#  /dev/disk/by-vdev/A0-part1 -> ../../dm-1
667	#  /dev/disk/by-vdev/A0-part2 -> ../../dm-3
668	#
669	# Warning: The following grep pattern will misidentify whole-disk
670	#          devices whose names end with 'p' followed by a string of
671	#          digits as partitions, causing alias creation to fail. This
672	#          ambiguity seems unavoidable, so devices using this facility
673	#          must not use such names.
674	DM_PART=
675	if echo "$DM_NAME" | grep -q -E 'p[0-9][0-9]*$' ; then
676		if [ "$DEVTYPE" != "partition" ] ; then
677			# Match p[number], remove the 'p' and prepend "-part"
678			DM_PART=$(echo "$DM_NAME" |
679			    awk 'match($0,/p[0-9]+$/) {print "-part"substr($0,RSTART+1,RLENGTH-1)}')
680		fi
681	fi
682
683	# DEVLINKS attribute must have been populated by already-run udev rules.
684	for link in $DEVLINKS ; do
685		# Remove partition information to match key of top-level device.
686		if [ -n "$DM_PART" ] ; then
687			link=$(echo "$link" | sed 's/p[0-9][0-9]*$//')
688		fi
689		# Check both the fully qualified and the base name of link.
690		for l in $link ${link##*/} ; do
691			if [ ! -z "$l" ]; then
692				alias=$(awk -v var="$l" '($1 == "alias") && \
693					($3 == var) \
694					{ print $2; exit }' $CONFIG)
695				if [ -n "$alias" ] ; then
696					echo "${alias}${DM_PART}"
697					return
698				fi
699			fi
700		done
701	done
702}
703
704# main
705while getopts 'c:d:eg:jmp:h' OPTION; do
706	case ${OPTION} in
707	c)
708		CONFIG=${OPTARG}
709		;;
710	d)
711		DEV=${OPTARG}
712		;;
713	e)
714	# When udev sees a scsi_generic device, it calls this script with -e to
715	# create the enclosure device symlinks only.  We also need
716	# "enclosure_symlinks yes" set in vdev_id.config to actually create the
717	# symlink.
718	ENCLOSURE_MODE=$(awk '{if ($1 == "enclosure_symlinks") \
719		print $2}' "$CONFIG")
720
721	if [ "$ENCLOSURE_MODE" != "yes" ] ; then
722		exit 0
723	fi
724		;;
725	g)
726		TOPOLOGY=$OPTARG
727		;;
728	p)
729		PHYS_PER_PORT=${OPTARG}
730		;;
731	j)
732		MULTIJBOD_MODE=yes
733		;;
734	m)
735		MULTIPATH_MODE=yes
736		;;
737	h)
738		usage
739		;;
740	esac
741done
742
743if [ ! -r "$CONFIG" ] ; then
744	echo "Error: Config file \"$CONFIG\" not found"
745	exit 1
746fi
747
748if [ -z "$DEV" ] && [ -z "$ENCLOSURE_MODE" ] ; then
749	echo "Error: missing required option -d"
750	exit 1
751fi
752
753if [ -z "$TOPOLOGY" ] ; then
754	TOPOLOGY=$(awk '($1 == "topology") {print $2; exit}' "$CONFIG")
755fi
756
757if [ -z "$BAY" ] ; then
758	BAY=$(awk '($1 == "slot") {print $2; exit}' "$CONFIG")
759fi
760
761ZPAD=$(awk '($1 == "zpad_slot") {print $2; exit}' "$CONFIG")
762
763TOPOLOGY=${TOPOLOGY:-sas_direct}
764
765# Should we create /dev/by-enclosure symlinks?
766if [ "$ENCLOSURE_MODE" = "yes" ] && [ "$TOPOLOGY" = "sas_direct" ] ; then
767	ID_ENCLOSURE=$(enclosure_handler)
768	if [ -z "$ID_ENCLOSURE" ] ; then
769		exit 0
770	fi
771
772	# Just create the symlinks to the enclosure devices and then exit.
773	ENCLOSURE_PREFIX=$(awk '/enclosure_symlinks_prefix/{print $2}' "$CONFIG")
774	if [ -z "$ENCLOSURE_PREFIX" ] ; then
775		ENCLOSURE_PREFIX="enc"
776	fi
777	echo "ID_ENCLOSURE=$ID_ENCLOSURE"
778	echo "ID_ENCLOSURE_PATH=by-enclosure/$ENCLOSURE_PREFIX-$ID_ENCLOSURE"
779	exit 0
780fi
781
782# First check if an alias was defined for this device.
783ID_VDEV=$(alias_handler)
784
785if [ -z "$ID_VDEV" ] ; then
786	BAY=${BAY:-bay}
787	case $TOPOLOGY in
788		sas_direct|sas_switch)
789			ID_VDEV=$(sas_handler)
790			;;
791		scsi)
792			ID_VDEV=$(scsi_handler)
793			;;
794		*)
795			echo "Error: unknown topology $TOPOLOGY"
796			exit 1
797			;;
798	esac
799fi
800
801if [ -n "$ID_VDEV" ] ; then
802	echo "ID_VDEV=${ID_VDEV}"
803	echo "ID_VDEV_PATH=disk/by-vdev/${ID_VDEV}"
804fi
805