#! /bin/bash
# Copyright (c) 2004 International Business Machines
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# bootlist - command to display and/or update the bootlist in nvram.
#
# author Nathan Fontenot <nfont@linux.vnet.ibm.com>
#

OFPATHNAME=/usr/sbin/ofpathname
NVRAM=/usr/sbin/nvram
PSERIES_PLATFORM=$(dirname $0)/pseries_platform

#
# usage
# 
usage()
{
    echo "Usage: $0 -m {normal|service|both} -o | -r"
    echo "       $0 -m {normal|service|both} [-o | -r] -f <file>"
    echo "       $0 -m {normal|service|both} [-o | -r] <device_list>"
    echo "View and update the system boot lists"
    echo ""
    echo "  -m {normal|service|both}"
    echo "                   Specify the boot mode for boolist manipulation"
    echo "  -o               Display bootlist entries as logical device names"
    echo "  -r               Display bootlist entries as Open Firmware device"
    echo "                   path names"
    echo "  -f file          Read the boolist device names from file"
    echo "  -?, --help       display this help information and exit"
    echo "  <device_list>    a space-separated list of devices, specified as"
    echo "                   logical device names or OF device path names,"
    echo "                   depending on whether the -o or -r option is specified"
    echo ""
    echo "Additional optional arguments for ethernet devices:"
    echo " bserver=<IP address of BOOTP server>"
    echo " gateway=<IP address of gateway>"
    echo " client=<IP address of the client>"
    echo " speed=<Network adapter speed>, default=auto"
    echo " duplex=<mode of the network adapter>, default=auto"
    echo ""
}

#
# update_eth_dev
# When specifying an ethernet device for the bootlist we need to also get
# the additional parameters for ethernet devices (i.e gateway, speed, ...)
#
# Please NOTE:  this routine does depend on global variables
#
update_eth_dev()
{
    local speed=auto
    local duplex=auto
    local bserver=0.0.0.0
    local gateway=0.0.0.0
    local client=0.0.0.0
    local eth_index=$[$ctr]
    local index=$[$ctr + 1]
    local found=1

    while [[ $found -eq 1 ]]; do
	found=0
        case ${LOGICAL_NAMES[$index]} in
	    speed*)		speed=${LOGICAL_NAMES[$index]##*=}
				found=1 ;;

	    duplex*)		duplex=${LOGICAL_NAMES[$index]##*=}
				found=1 ;;

	    bserver*)	 	bserver=${LOGICAL_NAMES[$index]##*=}
				found=1 ;;

	    gateway*)		gateway=${LOGICAL_NAMES[$index]##*=}
				found=1 ;;

	    client*)		client=${LOGICAL_NAMES[$index]##*=}
				found=1 ;;
    	esac

	if [[ $found -eq 1 ]]; then
	    index=$[$index + 1]
	    ctr=$[$ctr + 1]
	fi
    done

    # update the ethernet device
    OF_DEVPATH[$eth_index]=${OF_DEVPATH[$eth_index]}:speed=$speed,duplex=$duplex,$bserver,,$client,$gateway
}

#
# parse_eth_info
# Ethernet read from nvram (possibly) have additional data appended to 
# them specifying the gateway, speed, ...
#
# $1 ethernet device name
# $2 ethernet device data
# 
parse_eth_info()
{
    local eth_name=$1
    local eth_info=${2##*:}

    echo $eth_name

    # first the speed
    local item=${eth_info%%,*}
    if [[ -n $item ]]; then
	echo "    speed = ${item##*=}"
    fi

    # then the duplex
    eth_info=${eth_info#*,}
    item=${eth_info%%,*}
    if [[ -n $item ]]; then
	echo "    duplex = ${item##*=}"
    fi

    # then the BOOTP server
    eth_info=${eth_info#*,}
    item=${eth_info%%,*}
    if [[ -n $item ]]; then
	echo "    BOOTP Server: $item"
    fi
    
    # then the Mask
    eth_info=${eth_info#*,}
    item=${eth_info%%,*}
    if [[ -n $item ]]; then
	echo "    Mask: $item"
    fi
    
    # then the client
    eth_info=${eth_info#*,}
    item=${eth_info%%,*}
    if [[ -n $item ]]; then
	echo "    Client: $item"
    fi
    
    # then the Gateway
    eth_info=${eth_info#*,}
    item=${eth_info%%,*}
    if [[ -n $item ]]; then
	echo "    Gateway: $item"
    fi
} 

#
# get_logical_device_name
# Translate the provided boot device to its logical device name
#
# $1 device name to convert
#
get_logical_device_name()
{
    local devname=$1
    local logical_name

    logical_name=`$OFPATHNAME -l $devname 2>/dev/null`
    if [[ $? -ne 0 ]]; then
	echo ""
    else
	echo $logical_name
    fi
}

#
# get_of_device_name
# Translate the provided boot device to its OF device name
#
# $1 device name to convert
#
get_of_device_name()
{
    local devname=$1
    local of_name

    of_name=`$OFPATHNAME $devname 2>/dev/null`
    if [[ $? -ne 0 ]]; then
	echo ""
    else
	echo $of_name
    fi
}

#
# show_bootlist
# Retrieve a bootlist from nvram and print its contents
#
# $1 bootlist to print
# 
show_bootlist()
{
    local devlist=$1
    local i

    for i in `$NVRAM --print-config=${devlist} | sed 's/ /\n/g'`; do
	if [[ $TRANSLATE_NAMES = "yes" ]]; then
	    name=`get_logical_device_name $i`
	    if [[ -z $name ]]; then
	        echo "Could not translate $i to logical device name"
	    else
		case $name in
		    eth*)   parse_eth_info $name $i ;;
		       *)   echo $name ;; 
	        esac
	    fi
	else
	    echo $i
	fi
    done
}

#
# Main
#

. $PSERIES_PLATFORM
if [[ $platform != $PLATFORM_PSERIES_LPAR ]]; then
    echo "bootlist: is not supported on the $platform_name platform"
    exit 1
fi

# make sure we can parse names
if [[ ! -a $OFPATHNAME ]]; then
    echo "no $OFPATHNAME!!!"
fi

# make sure we can update nvram
if [[ ! -a $NVRAM ]]; then
    echo "no $NVRAM!!!"
fi

BOOTLIST_MODE=unknown

dm_to_part()
{
        local dmp=$1
        local dmdev=$2
        local sddev=$3
        local dmmapper

        partname=$(cat "/sys/block/$dmp/dm/name" 2>/dev/null | sed 's/[0-9]*$//g')
        diskname=$(cat "/sys/block/$dmdev/dm/name" 2>/dev/null)
        delim=${partname##$diskname}
        dmpmajmin=$(cat "/sys/block/$dmp/dev" 2>/dev/null)
        dmdevmajmin=$(cat "/sys/block/$dmdev/dev" 2>/dev/null)

        for dmapper in /dev/mapper/* ; do
                dmajor=$(stat -L --format="%t" $dmapper 2>/dev/null)
                dminor=$(stat -L --format="%T" $dmapper 2>/dev/null)
                dmajmin=$(printf "%d:%d" 0x$dmajor 0x$dminor)
                if [[ "$dmajmin" = "$dmdevmajmin" ]]; then
                        dmmapper=$dmapper;
                        break;
                fi
        done


        kpartx -p $delim -l $dmmapper | while read kp ; do
                kpname=${kp%% *}
                tmajor=$(stat -L --format="%t" /dev/mapper/$kpname 2>/dev/null)
                tminor=$(stat -L --format="%T" /dev/mapper/$kpname 2>/dev/null)
                tmajmin=$(printf "%d:%d" 0x$tmajor 0x$tminor)
                if [[ "$tmajmin" = "$dmpmajmin" ]]; then
                        partstart=${kp##* }
                        for part in `ls -1d /sys/block/$sddev/$sddev*`; do
                                pstart=$(cat $part/start 2>/dev/null)
                                if [[ "$pstart" -eq "$partstart" ]] ; then
                                        echo "${part##*/}"
                                        return
                                fi
                        done
                fi
        done
}

add_logical()
{
    local DEVNAME=$1
    local major minor majmin devno
    local startctr=$ctr

    if [[ ! -e $DEVNAME ]]; then
        DEVNAME=/dev/$DEVNAME
    fi

    if [[ ! -e $DEVNAME ]]; then
        LOGICAL_NAMES[$ctr]=$1
        ctr=$[$ctr + 1]
        return
    fi

    major=$(stat -L --format="%t" $DEVNAME 2>/dev/null)
    minor=$(stat -L --format="%T" $DEVNAME 2>/dev/null)
    majmin=$(printf "%d:%d" 0x$major 0x$minor)

    # Look for a matching multipath device

    for dm in /sys/block/dm*; do
        dmdev=${dm##*/}
        devno=$(cat $dm/dev 2>/dev/null)
        devmaj=${devno%:*}
        devmin=${devno#*:}

        if [[ ! -d $dm/slaves ]] ; then
            # Old kernel for which there is no good way to reliably map
	    # a kpartx device with its parent
            break;
        fi

        if [[ ! -d $dm/dm ]] ; then
            # Old kernel for which there is no good way to reliably map
	    # a kpartx device with its parent
            break;
        fi

        if [[ "$devno" = "$majmin" ]]; then
            for slave in $dm/slaves/*; do
                slavedev=${slave##*/}
                if [[ "$slavedev" == dm-* ]] ; then
                    for slave2 in $slave/slaves/*; do
                        slavedev2=${slave2##*/}
                        if [[ "$slavedev2" == dm-* ]] ; then
                            # dmdev is an LV on physical multipath partition
                            for slave3 in $slave2/slaves/*; do
                                slavedev3=${slave3##*/}
                                partdev=$(dm_to_part $slavedev $slavedev2 $slavedev3)
                                if [[ ! -z "$partdev" ]] ; then
                                    LOGICAL_NAMES[$ctr]=$partdev
                                    ctr=$[$ctr + 1]
                                fi
                             done
                        else
			     # /sys/block/dm-2/slave/dm-0/slaves/sdb
                             kp=$(kpartx -l /dev/$slavedev)

                             if [[ -z "$kp" ]] ; then
                                 # dmdev is an LV on physical multipath
				 # disk LV->DMDEV->DEV
                                 LOGICAL_NAMES[$ctr]=$slavedev2
                                 ctr=$[$ctr + 1]
                             else
                                 # dmdev is multipath partition of slave
				 # DMP->DMDEV->DEV
                                 partdev=$(dm_to_part $dmdev $slavedev $slavedev2)
                                 if [[ ! -z "$partdev" ]] ; then
                                     LOGICAL_NAMES[$ctr]=$partdev
                                     ctr=$[$ctr + 1]
                                 fi
                             fi
                        fi
                    done
                else
		    # DMDEV is a multipath device on a physical device or
		    # a LV on a disk partition
                    LOGICAL_NAMES[$ctr]=$slavedev
                    ctr=$[$ctr + 1]
                fi
            done
        fi
    done

    if [[ "$startctr" = "$ctr" ]] ; then
        LOGICAL_NAMES[$ctr]=$1
        ctr=$[$ctr + 1]
    fi
}

#
# Parse the command line arguements and put them into two parallel
# arrays, one to hold the logical device name and one to hold the 
# corresponding open firmware device path.
#
typeset -i ctr=0

while [[ -n $1 ]]; do
    if [[ $1 = "-o" ]]; then
        DISPLAY_BOOTLIST=yes
	TRANSLATE_NAMES=yes
    elif [[ $1 = "-r" ]]; then
        DISPLAY_BOOTLIST=yes
    elif [[ $1 = "-m" ]]; then
        shift
        if [[ ! -n $1 ]]; then
            echo "did not specify \"normal\" or \"service\" mode with -m" >&2
            usage
            exit -1
        fi

        if [[ $1 = "normal" ]]; then
            BOOTLIST_MODE=boot-device
        elif [[ $1 = "service" ]]; then
            BOOTLIST_MODE=diag-device
        elif [[ $1 = "both" ]]; then
            BOOTLIST_MODE=$1
        else
            echo "invalid mode specified with -m; must be \"normal\", \"service\" or \"both\"" >&2
            usage
            exit -1
        fi
    elif [[ $1 = "-f" ]]; then
        # get bootlist names from specified file
	if [[ ! -a $2 ]]; then
	    echo "file $2 does not exist" >&2
	fi

        for i in `cat $2 2>/dev/null`; do
	    add_logical $i
	done
	shift
    elif [[ $1 = "--help" ]]; then
       # display bootlist command help message
       usage
       exit 0
    elif [[ $1 = -* ]]; then
    	# catch any illegal flags here
	usage
	exit -1
    else
        # add this element to the array
	add_logical $1
    fi

    shift
done

if [[ ${BOOTLIST_MODE} = "unknown" ]]; then
    echo "The boot mode must be specified with the -m option" >&2
    usage
    exit -1
fi

# Now we need to convert all of the logical device names to
# open firmware device paths.
if [[ ${#LOGICAL_NAMES[*]} -ne 0 ]]; then
    ctr=0
    while [[ $ctr -lt ${#LOGICAL_NAMES[*]} ]]; do
        OF_DEVPATH[$ctr]=`get_logical_device_name ${LOGICAL_NAMES[$ctr]}`
        if [[ -z ${OF_DEVPATH[$ctr]} ]]; then
	    # See if this is an OF pathname
	    OF_DEVPATH[$ctr]=`get_of_device_name ${LOGICAL_NAMES[$ctr]}`
        fi

        if [[ -z ${OF_DEVPATH[$ctr]} ]]; then
	    echo "Device ${LOGICAL_NAMES[$ctr]} does not appear to be valid." >&2
	else
	    # See if this is an ethernet adapter.  If so, the next entries
	    # may be parameters for the bootlist entry.
	    ethdev=`get_logical_device_name ${OF_DEVPATH[$ctr]}`
	    ethdev=${ethdev%%[0-9]*}
	    if [[ $ethdev = "eth" ]]; then
		update_eth_dev
	    fi

            # bootlist entries cannot exceed more than 255 chars
            if [[ ${#OF_DEVPATH[$ctr]} -gt 255 ]]; then
                echo "Bootlist entries cannot exceed 255 characters" >&2
                echo "${OF_DEVPATH[$ctr]}" >&2
                exit -1
            fi
	fi

        ctr=$[$ctr + 1]
    done

    # We cannot have a bootlist with more than five entries
    if [[ ${#OF_DEVPATH[*]} -gt 5 ]]; then
        echo "More than five entries cannot be specified in the bootlist" >&2
        exit -1
    fi

    # update the bootlist in nvram
    if [[ $BOOTLIST_MODE = "both" ]]; then
        $NVRAM --update-config "boot-device=${OF_DEVPATH[*]}" -pcommon
        if [[ $? -ne 0 ]]; then
            echo "Could not update service-mode bootlist" >&2
        fi

        $NVRAM --update-config "diag-device=${OF_DEVPATH[*]}" -pcommon
        if [[ $? -ne 0 ]]; then
            echo "Could not update normal-mode bootlist" >&2
        fi
    else
        $NVRAM --update-config "${BOOTLIST_MODE}=${OF_DEVPATH[*]}" -pcommon
        if [[ $? -ne 0 ]]; then
            echo "Could not update bootlist!" >&2
        fi
    fi
fi

# Display the bootlist if desired
if [[ $DISPLAY_BOOTLIST = "yes" ]]; then
    if [[ $BOOTLIST_MODE = "both" ]]; then
	show_bootlist "boot-device"
	show_bootlist "diag-device"
    else
	show_bootlist $BOOTLIST_MODE
    fi
fi
