26d3ec265b19dab6aef844259a540ce2cb9620b9
[zfs.git] / cmd / zpool_layout / zpool_layout
1 #!/bin/bash
2 #
3 # Set BUSES and PORTS to match the topology of your system.  As each
4 # port is enumerated it will be assigned the next channel name.  The
5 # current script enumerates each port on a bus before moving on to
6 # enumerate the next bus.
7 #
8 # Every distribution, version of udev, and type of attached storage
9 # seems to result in slightly different formatting of the by-path
10 # name.   For this reason you may need to adjust the parsing below
11 # to suit your needs.  This is one of the reasons to use a custom
12 # /etc/zfs/zdev.conf file, it allows the by-path naming convertion
13 # to change and still keep the simple <channel><rank> naming.
14 #
15 AWK=${AWK:-/usr/bin/awk}
16 CONFIG=${CONFIG:-/etc/zfs/zdev.conf}
17 BUSES=( 01 02 03 )
18 PORTS=( 4 0 )
19 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 )
20 TRIGGER="no"
21 MAPPING=linux
22
23
24 usage() {
25         cat << EOF
26 Usage: zpool_layout [-th] [-c file] [-b buses] [-p ports] [-n channels] [-m map]
27   -c    Alternate config file [default=${CONFIG}]
28   -b    Enumerate buses [default="${BUSES[*]}"]
29   -p    Enumerate ports [default="${PORTS[*]}"]
30   -n    Channel names [default="A..Z"]
31   -t    Trigger and wait for udev to settle [default=${TRIGGER}]
32   -m    Slot mapping [default=${MAPPING}]
33   -h    Show this message
34 EOF
35         exit 0
36 }
37
38 while getopts 'c:b:p:n:m:th' OPTION; do
39         case ${OPTION} in
40         c)
41                 CONFIG=${OPTARG}
42                 ;;
43         b)
44                 BUSES=(${OPTARG})
45                 ;;
46         p)
47                 PORTS=(${OPTARG})
48                 ;;
49         n)
50                 CHANNELS=(${OPTARG})
51                 ;;
52         m)
53                 MAPPING=`readlink -e ${OPTARG}`
54                 ;;
55         t)
56                 TRIGGER=yes
57                 ;;
58         h)
59                 usage
60                 ;;
61         esac
62 done
63
64 # Verify mapping file exists if specified.
65 # Linux-Slot    Custom-Slot
66 if [ ${MAPPING} != "linux" ] && [ ! -e ${MAPPING} ]; then
67         echo "Error: Mapping file '${MAPPING}' does not exist"
68         exit 1
69 fi
70
71 # Save stdout as fd #8, then redirect stdout to the config file.
72 exec 8>&1
73 exec >${CONFIG}
74 pushd /dev/disk/by-path >/dev/null
75
76 map_slot() {
77         local LINUX_SLOT=$1
78         local MAPPED_SLOT=
79
80         if [ ${MAPPING} = "linux" ]; then
81                 MAPPED_SLOT=${LINUX_SLOT}
82         else
83                 MAPPED_SLOT=`${AWK} "\\$1 == ${LINUX_SLOT} && !/^#/ \
84                         { print \\$2; exit }" $MAPPING`
85         fi
86         printf "%d" ${MAPPED_SLOT}
87 }
88
89 # Generate comment header.
90 echo "#"
91 echo "# Custom /dev/disk/by-path to /dev/disk/zpool mapping, "
92 echo "# based of the following physical cable layout."
93 echo "#"
94
95 # Generate host port layout table for comment header.
96 echo "# ------------------ Host Port Layout ---------------------"
97 echo -n "#          "
98 for (( i=0; i<${#BUSES[*]}; i++ )); do
99         printf "%-8d" ${BUSES[$i]}
100 done
101 echo
102
103 for (( i=0, k=0; i<${#PORTS[*]}; i++ )); do
104         printf "# Port %-2d  " ${PORTS[$i]}
105
106         for (( j=0; j<${#BUSES[*]}; j++, k++ )); do
107                 let k=$j*${#PORTS[*]}+$i
108                 printf "%-8s" ${CHANNELS[$k]}
109         done
110         echo
111 done
112 echo "#"
113
114 # Generate channel/disk layout table for comment header.
115 echo "# ----------------- Channel/Disk Layout -------------------"
116 echo "# Channel  Disks"
117 for (( i=0, k=0; i<${#BUSES[*]}; i++ )); do
118         for (( j=0; j<${#PORTS[*]}; j++, k++ )); do
119                 printf "# %-9s" ${CHANNELS[$k]}
120                 ls *:${BUSES[$i]}:*:${PORTS[$j]}* 2>/dev/null | \
121                         cut -f7 -d'-' | sort -u -n | tr '\n' ','
122                 echo
123         done
124 done
125 echo "#"
126
127 # Generate mapping from <channel><rank> to by-path name.
128 for (( i=0, k=0; i<${#BUSES[*]}; i++ )); do
129         for (( j=0; j<${#PORTS[*]}; j++, k++ )); do
130                 BYPATH=(`ls *:${BUSES[$i]}:*:${PORTS[$j]}* 2>/dev/null | \
131                         grep -v part | sort -n -k7 -t'-' | cut -f1-6 -d'-'`)
132                 SLOTS=(`ls *:${BUSES[$i]}:*:${PORTS[$j]}* 2>/dev/null |  \
133                         grep -v part | sort -n -k7 -t'-' | cut -f7 -d'-'`)
134                 TMP_FILE=`mktemp`
135
136                 for (( l=0; l<${#SLOTS[*]}; l++ )); do
137                         MAPPED_SLOT=`map_slot ${SLOTS[$l]}`
138                         printf "%s%d\t%s-%d\n" \
139                                 ${CHANNELS[$k]} ${MAPPED_SLOT} \
140                                 ${BYPATH[$l]} ${SLOTS[$l]} >>${TMP_FILE}
141                 done
142
143                 echo
144                 echo -n "# Channel ${CHANNELS[$k]}, "
145                 echo "Bus ${BUSES[$i]}, Port ${PORTS[$j]}"
146                 cat ${TMP_FILE} | sort -n -k2 -t${CHANNELS[$k]}
147                 rm -f ${TMP_FILE}
148         done
149 done
150
151 # Restore stdout from fd #8 and close fd #8.
152 exec 1>&8 8>&-
153 popd >/dev/null
154
155 if [ ${TRIGGER} = "yes" ]; then
156         udevadm trigger --action=change --subsystem-match=block
157         udevadm settle
158 fi
159
160 exit 0