#!/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. # # Every distribution, version of udev, and type of attached storage # seems to result in slightly different formatting of the by-path # name. For this reason you may need to adjust the parsing below # to suit your needs. This is one of the reasons to use a custom # /etc/zfs/zdev.conf file, it allows the by-path naming convertion # to change and still keep the simple naming. # AWK=${AWK:-/bin/awk} 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="no" MAPPING=linux usage() { cat << EOF Usage: zpool_layout [-th] [-c file] [-b buses] [-p ports] [-n channels] [-m map] -c Alternate config file [default=${CONFIG}] -b Enumerate buses [default="${BUSES[*]}"] -p Enumerate ports [default="${PORTS[*]}"] -n Channel names [default="A..Z"] -t Trigger and wait for udev to settle [default=${TRIGGER}] -m Slot mapping [default=${MAPPING}] -h Show this message EOF exit 0 } while getopts 'c:b:p:n:m:th' OPTION; do case ${OPTION} in c) CONFIG=${OPTARG} ;; b) BUSES=(${OPTARG}) ;; p) PORTS=(${OPTARG}) ;; n) CHANNELS=(${OPTARG}) ;; m) MAPPING=`readlink -e ${OPTARG}` ;; t) TRIGGER=yes ;; h) usage ;; esac done # Verify mapping file exists if specified. # Linux-Slot Custom-Slot if [ ${MAPPING} != "linux" ] && [ ! -e ${MAPPING} ]; then echo "Error: Mapping file '${MAPPING}' does not exist" exit 1 fi # Save stdout as fd #8, then redirect stdout to the config file. exec 8>&1 exec >${CONFIG} pushd /dev/disk/by-path >/dev/null map_slot() { local LINUX_SLOT=$1 local MAPPED_SLOT= if [ ${MAPPING} = "linux" ]; then MAPPED_SLOT=${LINUX_SLOT} else MAPPED_SLOT=`${AWK} "\\$1 == ${LINUX_SLOT} && !/^#/ \ { print \\$2; exit }" $MAPPING` fi printf "%d" ${MAPPED_SLOT} } # 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 -u -n | tr '\n' ',' echo done done echo "#" # Generate mapping from to by-path name. for (( i=0, k=0; i<${#BUSES[*]}; i++ )); do for (( j=0; j<${#PORTS[*]}; j++, k++ )); do BYPATH=(`ls *:${BUSES[$i]}:*:${PORTS[$j]}* 2>/dev/null | \ grep -v part | sort -n -k7 -t'-' | cut -f1-6 -d'-'`) SLOTS=(`ls *:${BUSES[$i]}:*:${PORTS[$j]}* 2>/dev/null | \ grep -v part | sort -n -k7 -t'-' | cut -f7 -d'-'`) TMP_FILE=`mktemp` for (( l=0; l<${#SLOTS[*]}; l++ )); do MAPPED_SLOT=`map_slot ${SLOTS[$l]}` printf "%s%d\t%s-%d\n" \ ${CHANNELS[$k]} ${MAPPED_SLOT} \ ${BYPATH[$l]} ${SLOTS[$l]} >>${TMP_FILE} done echo echo -n "# Channel ${CHANNELS[$k]}, " echo "Bus ${BUSES[$i]}, Port ${PORTS[$j]}" cat ${TMP_FILE} | sort -n -k2 -t${CHANNELS[$k]} rm -f ${TMP_FILE} done done # Restore stdout from fd #8 and close fd #8. exec 1>&8 8>&- popd >/dev/null if [ ${TRIGGER} = "yes" ]; then udevadm trigger udevadm settle fi exit 0