#!/bin/ksh93 MAPFILE='/lib/udev/baymap' RULEFILE='/lib/udev/rules.d/61-baylinks.rules' DEVPREFIX='chassis' RAMFSHOOK='/usr/share/initramfs-tools/hooks/baymaps' ROOTFS='/' typeset -A PHYMAP=( # SLOT2PHY MAP - at least for LSI based HBAs it is the same. # IDX == Slot# VAL == PHY# ['SAS9207-8i']='3:2:1:0:7:6:5:4' ['SAS9211-8i']='3:2:1:0:7:6:5:4' ['SAS9201-16i']='2:3:1:0:7:6:5:4:11:10:9:8:15:14:13:12' ['SAS9300-8i']='2:3:1:0:6:7:5:4' ['SAS9305-16i']='2:3:1:0:6:7:5:4:18:19:17:16:22:23:21:20' ['SAS9305-24i']='2:3:1:0:6:7:5:4:10:11:9:8:14:15:13:12:18:19:17:16:22:23:21:20' ['VBox MPT Fusion']='0:1:2:3:4:5:6:7' ) function initrdNotice { print ' If you are booting via initrd, you should update the related image. E.g.: update-initramfs -k all -u ' } function installHook { HOOK='#!/bin/sh -e PREREQS="udev" # Since this stuff gets sourced in rather than execed by a very brain damaged, # limited and buggy shell, we need to use its poor, bloated syntax and can not # make use of advanced features provides by ksh93 ... :(((((((( MAPSCRIPT="/lib/udev/bayLinks.sh" # Just to avoid copy over itself if [ -z "${DESTDIR}" ]; then DESTDIR=/tmp fi if [ -n "$1" -a "$1" = "prereqs" ]; then echo "${PREREQS}" exit 0 fi [ ! -e "${MAPSCRIPT}" ] && return . /usr/share/initramfs-tools/hook-functions while read LINE ; do if [ "${LINE#MAPFILE=}" != "${LINE}" ]; then MAPFILE="${LINE#MAPFILE=?}" MAPFILE="${MAPFILE%?}" elif [ "${LINE#RULEFILE=}" != "${LINE}" ]; then RULEFILE="${LINE#RULEFILE=?}" RULEFILE="${RULEFILE%?}" break fi done < "${MAPSCRIPT}" if [ -z "${MAPFILE}" -o -z "${RULEFILE}" ] ; then echo "${MAPSCRIPT} script does not contain MAPFILE and RULEFILE settings!" exit 1 fi cp -p "${MAPSCRIPT}" "${DESTDIR}/lib/udev/" if [ -e "${RULEFILE}" ]; then cp -pL "${RULEFILE}" "${DESTDIR}/lib/udev/rules.d/" fi if [ -e "${MAPFILE}" ]; then cp -pL "${MAPFILE}" "${DESTDIR}/lib/udev/" fi copy_exec /bin/ksh93 /bin ' if (( DRY )); then print "${HOOK}" else print "${HOOK}" >"${ROOTFS}/${RAMFSHOOK}" && \ print "Hook installed as '${ROOTFS}/${RAMFSHOOK}'" chmod 755 "${ROOTFS}/${RAMFSHOOK}" fi } function getSlot2phyMap { typeset X BOARD="$1" X=${PHYMAP["${BOARD}"]} if [[ -z $X ]]; then if [[ ${BOARD:0:5} == 'SAS92' ]]; then if [[ ${BOARD: -3:3} == '16i' ]]; then X=${PHYMAP['SAS9201-16i']} else X=${PHYMAP['SAS9207-8i']} fi else # assume SAS93 if [[ ${BOARD: -2:2} == '8i' ]]; then X=${PHYMAP['SAS9300-8i']} elif [[ ${BOARD: -3:3} == '16i' ]]; then X=${PHYMAP['SAS9305-16i']} elif [[ -n ${PHYMAP["${BOARD}"]} ]]; then X=${PHYMAP["${BOARD}"]} else # assume 24i X=${PHYMAP['SAS9305-24i']} fi fi fi print "$X" } function makeMap { if [[ ! -e /sys/class/sas_phy ]]; then print "No SAS phys found." exit 0 fi typeset X L PPHY PORT LAST= BOX MAP="# ${MAPFILE}\n\n" DRIVER= OK= typeset -ui D=0 OFFSET_INT=0 OFFSET_EXT=0 MAX=0 H MAP+="# Generated by ${FPROG} on ${ date ; }\n" MAP+='# Format: SAS-CTRL-PCI-Slot PHY BOX_LABEL BAY_LABEL CURRENT_PHY CURRENT_PORT # (last 2 are informational and should be ignored) # nc .. not connected, i.e. no end device at the time of scanning # /sys/${SAS-CTRL-PCI-Slot}/host0/phy-0:${PHY}/port/end_device*/target*/*/block/ # should show the same sd*, that /dev/chassis/${BOX_LABEL}/${BAY_LABEL}/ shows # if the map is correct. ' ls -l /sys/class/sas_phy/phy-* | while read L ; do PPHY="${L#*../..}" PPHY="${PPHY%/*/*}" X=${ ls -l /sys${PPHY}/port 2>/dev/null; } PORT=${X##*/} [[ -z ${PORT} ]] && PORT='nc' # not connected # new usable PHY found, e.g. # /devices/pci0000:00/0000:00:02.0/0000:02:00.0/host0/phy-0:1 if [[ ${LAST} != ${PPHY%/*} ]]; then # new HBA if [[ -n ${LAST} ]]; then [[ ${BOX} == 'SYS' ]] && (( OFFSET_INT+=MAX+1 )) || \ (( OFFSET_EXT+=MAX+1 )) MAX=0 # reset PHY counter for this HBA fi LAST="${PPHY%/*}" X="/sys${LAST}/scsi_host/${LAST##*/}/board_name" unset SLOT2PHY; typeset SLOT2PHY unset PHY2SLOT; typeset -A PHY2SLOT if [[ -r $X ]]; then BOX=$(<$X) SLOT2PHY=${ getSlot2phyMap "${BOX}" ; } # sufficient enough - one may manually adjust [[ ${BOX: -1:1} == 'e' ]] && BOX='EXT' || BOX='SYS' else BOX='SYS' fi if [[ -z ${SLOT2PHY} ]]; then X="/sys${LAST}/scsi_host/${LAST##*/}/proc_name" if [[ ${DRIVER} == 'mpt3sas' ]]; then X=${PHYMAP['SAS9305-24i']} SLOT2PHY=( ${X//:/ } ) elif [[ ${DRIVER} == 'mpt2sas' ]]; then X=${PHYMAP['SAS9201-16i']} SLOT2PHY=( ${X//:/ } ) else print -u2 'Using generic map which certainly does not' \ 'match your HBA' SLOT2PHY=( {0..23} ) fi else SLOT2PHY=( ${SLOT2PHY//:/ } ) fi H=0 for X in ${SLOT2PHY[@]} ; do PHY2SLOT["phy$X"]="$H" (( H++ )) done fi D=${PPHY##*:} # phy X=${PHY2SLOT["phy$D"]} # slot [[ -z $X ]] && continue # some HBAs have not all PHYs connected D=$X (( D > MAX )) && MAX=$D # max. slotNo seen [[ ${BOX} == 'SYS' ]] && (( H=OFFSET_INT+D )) || (( H=OFFSET_EXT+D )) X=${PPHY%/*/*} L=${.sh.match} MAP+="${OK}${X#/devices} ${L##*:} ${BOX} HDD$H\t${PPHY##*/} ${PORT}\n" done if (( DRY )); then print "${MAP}" else print -n "${MAP}" >"${ROOTFS}/${MAPFILE}" && \ print "Baymap installed as '${ROOTFS}/${MAPFILE}'" fi initrdNotice } function makeRule { typeset RULE='# SAS phy <-> HDD label mapper # '"${RULEFILE}"' SUBSYSTEM!="block", GOTO="baylabel_end" KERNEL!="sd?*", GOTO="baylabel_end" # NOTE: ACTION=="add" is not sufficient, because a simple "parted -lm" would # trigger a change event, which would remove the symlink, if it is not added # again by the script! ENV{DEVTYPE}=="disk", PROGRAM="bayLinks.sh $devpath", RESULT=="?*", SYMLINK+="%c/disk %c{1}/%c{2}" ACTION!="remove", ENV{DEVTYPE}=="partition", PROGRAM="bayLinks.sh $devpath", RESULT=="?*", SYMLINK+="%c{1}/p%n %c{1}/%c{2}p%n" LABEL="baylabel_end" ' if (( DRY )); then print "${RULE}" else print -n "${RULE}" >"${ROOTFS}/${RULEFILE}" && \ print "Rule installed as '${ROOTFS}/${RULEFILE}'" fi initrdNotice } function lookup { integer DEBUG=1 if (( DEBUG )); then typeset D='/dev/.initramfs' T [ -d $D ] || D='/tmp' T="$D/${ date '+%s-%N' ; }.lst" set >"$T" || true fi # nothing to do ? [[ -z $1 || ! -f ${ROOTFS}/${MAPFILE} ]] && return 0 # initrd.img might not contain a regular ls -> what an ancient crap typeset LS=${ whence ls ; } if [[ -z ${LS} ]]; then if [[ -x /bin/ls ]]; then LS='/bin/ls' elif [[ -x /bin/busybox ]]; then busybox ln -s busybox /bin/ls LS='/bin/ls' else print -u2 "WARNING: ls is required for baymaps" fi fi typeset DEVPATH="$1" PCI PHY BOX LABEL TAIL DST_PORT="${DEVPATH%/end_device-*}" DST_PCI="${DST_PORT%/host*}" DST_PCI="${DST_PCI#/sys}" (( DEBUG )) && \ print "\n\nLooking up ${DEVPATH} aka \n${DST_PORT}\n" >>"$T" || true while read PCI PHY BOX LABEL TAIL; do # ignore empty lines and comments [[ -z ${PCI} || ${PCI:0:1} == '#' || -z ${LABEL} ]] && continue [[ ${DST_PCI} != "/devices${PCI}" ]] && continue P=${ ${LS} -l /sys/${DST_PORT}/phy-[0-9]*:${PHY} 2>/dev/null ; } if [[ -n ${P} ]]; then print "${DEVPREFIX}/${BOX}/${LABEL} ${LABEL}" (( DEBUG )) && print "${DEVPREFIX}/${BOX}/${LABEL} ${LABEL}" \ >>"$T" || true return 0 fi done <"${ROOTFS}/${MAPFILE}" return 0 } function fireUdev { typeset DEVS=${ ls -1 /dev/sd[a-z] 2>/dev/null ; } F [[ -z ${DEVS} ]] && return 0 for D in ${DEVS} ; do udevadm trigger --action=add $D done } integer MKMAP=0 MKRULE=0 DRY=0 INSTHOOK=0 UDEV=0 IDX FPROG=${.sh.file} PROG=${FPROG##*/} function showUsage { typeset WHAT="$1" X='--man' [[ -z ${WHAT} ]] && WHAT='MAIN' && X='-?' getopts -a "${PROG}" "${ print ${USAGE}; }" OPT $X } USAGE='[-?1.1 ] [-copyright?Copyright (c) 2014 Jens Elkner. All rights reserved.] [-license?CDDL 1.0] [+NAME?'"${PROG}"' - manage physical SAS links wrt. HDD labels] [+DESCRIPTION?This script can be used on \bLinux\b to generate a SAS phy <-> HDD label map, which can in turn be used by \budev\b(7) to create \b/dev/'"${DEVPREFIX}"'/\b\abox\a/\abay\a\b/disk\b links to the related \b/dev/sd\b\a?\a to obtain a \bstable naming\b. This is required, because Linux assigns device names on a first-come, first-served basis, and thus e.g. /dev/sda might be currently assigned to the HDD in bay 0 and on the next boot (especially when the boot order has been changed via the SAS controller ROM setup) /dev/sda might be assigned to the HDD in bay 7.] [+?If no option is given, the script assumes, that it got called by \budev\b(7). In this case the operand \bdevpath\b as well as the baymap file \b'"${MAPFILE}"'\b are required. They are used to lookup the related mapping and spit out the symlink to add by udev. The baymap file can be produced with this script. Because not all SAS controllers might be initialized when the event for a certain disk gets fired, the mapping cannot be generated on the fly.] [h:help?Print this help and exit.] [F:functions?Print a list of all functions of this script.] [T:trace]:[functionList?A comma separated list of functions of this script to trace (convinience for troubleshooting).] [n:dry?Just print the related map or rule to stdout, but do not install it.] [r:rule?Generate the rule file, which makes use of this script.] [R:ramfs?Generate the initramfs hook, so that all related files get copied to the initrd image and thus the script can fire on boot.] [m:map?Generate the baymap file to be used by this script.] [u:udevs?Triggers an udev add event for all /dev/sda-z.] [D:destdir]:[path?The directory to use as virtual root when writing/reading files except those beneath /dev, /sys and /proc.] \n\n[\adevpath\a] ' while getopts -a "${PROG}" "${ print ${USAGE}; }" OPT ; do case ${OPT} in h) showUsage MAIN ; exit 0 ;; F) typeset +f && exit 0 ;; T) if [[ ${OPTARG} == 'ALL' ]]; then typeset -ft ${ typeset +f ; } else typeset -ft ${OPTARG//,/ } fi ;; n) DRY=1 ;; m) MKMAP=1 ;; r) MKRULE=1 ;; R) INSTHOOK=1 ;; D) if [[ ! -d ${OPTARG} ]]; then print -u2 "Directory '${OPTARG}' does not exist - exiting!" exit 1 fi ROOTFS="${OPTARG%%/}" ;; u) UDEV=1 ;; *) showUsage ;; esac done (( IDX=OPTIND-1 )) shift $IDX (( MKRULE )) && makeRule (( MKMAP )) && makeMap (( INSTHOOK )) && installHook (( UDEV )) && fireUdev if [[ -n $1 ]]; then lookup "$1" elif (( MKRULE + MKMAP + INSTHOOK + UDEV == 0 )); then showUsage fi # vim: ts=4 sw=4 filetype=sh