From 7f939f28eb56b50571f236be54b3eaa2075a09ea Mon Sep 17 00:00:00 2001
From: Brian Behlendorf <behlendorf1@llnl.gov>
Date: Mon, 16 Aug 2010 15:54:06 -0700
Subject: [PATCH] Add zpool_layout command

The zpool_layout command is designed to automatically scan the
udev /dev/disk/by-path directory and generate a /etc/zfs/zdev.conf
file.  It does this by enumerating the disks attached to the
specified buses/ports and sequentially mapping them to short
<channel><rank> names in /dev/disk/zpool/.  This tool should only
be run after all the available disks have been discovered.  And
the resulting config file does not need to be regenerated unless
your backend configuration changes.
---
 cmd/Makefile.am               |   2 +-
 cmd/zpool_layout/Makefile.am  |   1 +
 cmd/zpool_layout/zpool_layout | 120 ++++++++++++++++++++++++++++++++++
 configure.ac                  |   1 +
 4 files changed, 123 insertions(+), 1 deletion(-)
 create mode 100644 cmd/zpool_layout/Makefile.am
 create mode 100755 cmd/zpool_layout/zpool_layout

diff --git a/cmd/Makefile.am b/cmd/Makefile.am
index 98794d574e..954f8565cb 100644
--- a/cmd/Makefile.am
+++ b/cmd/Makefile.am
@@ -1 +1 @@
-SUBDIRS = zfs zpool zpool_id zdb zinject ztest
+SUBDIRS = zfs zpool zpool_id zpool_layout zdb zinject ztest
diff --git a/cmd/zpool_layout/Makefile.am b/cmd/zpool_layout/Makefile.am
new file mode 100644
index 0000000000..b17e6a3627
--- /dev/null
+++ b/cmd/zpool_layout/Makefile.am
@@ -0,0 +1 @@
+dist_bin_SCRIPTS = zpool_layout
diff --git a/cmd/zpool_layout/zpool_layout b/cmd/zpool_layout/zpool_layout
new file mode 100755
index 0000000000..e88cbe3c02
--- /dev/null
+++ b/cmd/zpool_layout/zpool_layout
@@ -0,0 +1,120 @@
+#!/bin/bash
+#
+# Set BUSES and PORTS to match the topology of your system.  As each
+# port is enumerated it will be assigned the next channel name.  The
+# current script enumerates each port on a bus before moving on to
+# enumerate the next bus.
+#
+CONFIG=${CONFIG:-/etc/zfs/zdev.conf}
+BUSES=( 01 02 03 )
+PORTS=( 4 0 )
+CHANNELS=( A B C D E F G H I J K L M N O P Q R S T U V W X Y Z )
+TRIGGER=
+
+usage() {
+	cat << EOF
+Usage: zpool_layout [-th] [-c file] [-b buses] [-p ports] [-n channels]
+  -c    Alternate config file [default=/etc/zfs/zdev.conf]
+  -b    Enumerate buses [default="01 02 03"]
+  -p    Enumerate ports [default="4 0"]
+  -n    Channel names [default="A..Z"]
+  -t    Trigger and wait for udev to settle [default=no]
+  -h    Show this message
+EOF
+	exit 0
+}
+
+while getopts 'c:b:p:n:th' OPTION; do
+	case ${OPTION} in
+	c)
+		CONFIG=${OPTARG}
+		;;
+	b)
+		BUSES=(${OPTARG})
+		;;
+	p)
+		PORTS=(${OPTARG})
+		;;
+	n)
+		CHANNELS=(${OPTARG})
+		;;
+	t)
+		TRIGGER=1
+		;;
+	h)
+		usage
+		;;
+	esac
+done
+
+# Save stdout as fd #8, then redirect stdout to the config file.
+exec 8>&1
+exec >${CONFIG}
+pushd /dev/disk/by-path >/dev/null
+
+# Generate comment header.
+echo "#"
+echo "# Custom /dev/disk/by-path to /dev/disk/zpool mapping, "
+echo "# based of the following physical cable layout."
+echo "#"
+
+# Generate host port layout table for comment header.
+echo "# ------------------ Host Port Layout ---------------------"
+echo -n "#          "
+for (( i=0; i<${#BUSES[*]}; i++ )); do
+	printf "%-8d" ${BUSES[$i]}
+done
+echo
+
+for (( i=0, k=0; i<${#PORTS[*]}; i++ )); do
+	printf "# Port %-2d  " ${PORTS[$i]}
+
+	for (( j=0; j<${#BUSES[*]}; j++, k++ )); do
+		let k=$j*${#PORTS[*]}+$i
+		printf "%-8s" ${CHANNELS[$k]}
+	done
+	echo
+done
+echo "#"
+
+# Generate channel/disk layout table for comment header.
+echo "# ----------------- Channel/Disk Layout -------------------"
+echo "# Channel  Disks"
+for (( i=0, k=0; i<${#BUSES[*]}; i++ )); do
+	for (( j=0; j<${#PORTS[*]}; j++, k++ )); do
+		printf "# %-9s" ${CHANNELS[$k]}
+		ls *:${BUSES[$i]}:*:${PORTS[$j]}* 2>/dev/null | \
+			cut -f7 -d'-' | sort -n | tr '\n' ','
+		echo
+	done
+done
+echo "#"
+
+# Generate mapping from <channel><rank> to by-path name.
+TMP_FILE=`mktemp`
+AWK=${AWK:-/bin/awk}
+
+for (( i=0, k=0; i<${#BUSES[*]}; i++ )); do
+	for (( j=0; j<${#PORTS[*]}; j++, k++ )); do
+		ls *:${BUSES[$i]}:*:${PORTS[$j]}* 2>/dev/null | \
+			sort -n -k7 -t'-'>${TMP_FILE}
+
+		echo
+		echo -n "# Channel ${CHANNELS[$k]}, "
+		echo "Bus ${BUSES[$i]}, Port ${PORTS[$j]}"
+		${AWK} -F '-' -v ch="${CHANNELS[$k]}" \
+			'{print ch$7 "\t" $0 }' ${TMP_FILE}
+	done
+done
+
+# Restore stdout from fd #8 and close fd #8.
+exec 1>&8 8>&-
+rm -f ${TMP_FILE}
+popd >/dev/null
+
+if [ ${TRIGGER} ]; then
+	udevadm trigger
+	udevadm settle
+fi
+
+exit 0
diff --git a/configure.ac b/configure.ac
index 2e4a2c5cda..728a2dd7c1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -74,6 +74,7 @@ AC_CONFIG_FILES([
 	cmd/zinject/Makefile
 	cmd/zpool/Makefile
 	cmd/zpool_id/Makefile
+	cmd/zpool_layout/Makefile
 	cmd/ztest/Makefile
 	module/Makefile
 	module/avl/Makefile