#! /bin/bash
# Copyright (c) 2004 International Business Machines
# Common Public License Version 1.0 (see COPYRIGHT)

#
# ofpathname - This utility provides a machanism for converting a logical
# device name to an open firmware device path, and vice versa.
#
# TODO: This script doesn't handle floppy drives and token ring devices,
#       perhaps they should be added in at some point.
#

OFPATHNAME="ofpathname"
VERSION="0.4"
FIND=/usr/bin/find
CAT=/bin/cat

# Usage statemnet
usage()
{
    echo "Usage: $OFPATHNAME [OPTION] DEVICE"
    echo "Report the Open Firmware device pathname for logical device DEVICE"
    echo ""
    echo "Optional arguments."
    echo "  -l               Convert Open Firmware device pathname to"
    echo "                   logical device name."
    echo "  -q, --quiet      do not report failures, exit quietly"
    echo "  -V, --version    display version information and exit"         
    echo "  -h, --help       display this help information and exit"
    echo ""
}

show_version()
{
    echo "$OFPATHNAME: Version $VERSION"
    echo "Written by: Nathan Fontenot"
}

# 
# err
# Common routine to print error messages for ofpathname.  Since most of the
# error messages can be generated in multiple places, we put all the text
# here to avoid errors in duplicating the messages.
#
# The first and only parameteris the error message number, all of which
# are defined below as ERR_*.
#
ERR_NO_OFPATH=1
ERR_NO_SYSFS=2
ERR_NO_SYSFS_DEVINFO=3
ERR_NOT_CONFIG=4
ERR_NO_LOGDEV=5
err()
{
    local emsg=$1
    if [[ -z $be_quiet ]]; then	
	case $emsg in
	    1)	echo "$OFPATHNAME: Could not retrieve Open Firmware device path"
	        echo "            for logical device \"$DEVNAME\"." ;;
	    
	    2)  echo "$OFPATHNAME: sysfs (/sys) is needed and does not appear"
		echo "            to be mounted on this system." ;;
	    
	    3)  echo "$OFPATHNAME: Could not find sysfs information for logical"
		echo "            device \"$DEVNAME\"." ;;
	    
	    4)  echo "$OFPATHANME: Logical device \"$DEVNAME\" does not appear"
		echo "            to be configured." ;;

	    5)  echo "$OFPATHNAME: Could not retrieve logical device name for"
		echo "            Open Firmware path \"$DEVNAME\"."
	esac
    fi

    exit 1
}

#
# get_link
# return the directory path that a link points to.
# The only parameter is the link name.
#
get_link()
{
    echo `ls -l $ln_name 2>/dev/null | awk -F"->" '{print $2}'`
}

#
# get_hbtl
# Given a path that ends in an HBTL (Host:Bus:Target:LUN), break it apart
# into its constituent parts in the global vars HOST, BUS, TARGET and LUN
#
# #1 path ending in HBTL
#
get_hbtl()
{
    HBTL=${1##*/}
    HOST=${hbtl%%:*}
    hbtl=${hbtl#*:}
    BUS=${hbtl%%:*}
    hbtl=${hbtl#*:}
    TARGET=${hbtl%%:*}
    LUN=${hbtl#*:}
}

#
# get_vdisk_no
# Given a path that ends in an HBTL, convert the HBTL values into a 
# virtual disk number (not sure what the real terminology is for it).
# To do the conversion, the HBTL (A:B:C:D) is split apart and 
# calculated as;
#     no = (0x8000000000000000 | B << 8 | C << 5 | D)
# 
# $1 path ending in HBTL
#
get_vdisk_no()
{
    get_hbtl $1

    local B C D
    typeset -i B C D

    B=$((0x$BUS << 8))
    C=$((0x$TARGET << 5))
    D=$((0x$LUN))

    local vdiskno vdisk
    typeset -i vdiskno
    vdiskno=$((0x8000000000000000 | $B | $C | $D ))
    vdisk=${vdiskno##-}

    echo `bc << END
ibase=10
obase=16
$vdisk
END`
}

#
# get_scsi_host
# Construct the scsi@XXX part of a Open Firmware device path from a
# given device directory
# 
# $1 - device 
#
get_scsi_host()
{
    local device_dir=$1
    local dir
    local scsi_name
    
    # build the scsi name 
    cd /sys/class/scsi_host
    for dir in `$FIND . -name $device_dir -follow`; do
        goto_dir $dir "unique_id"
        scsi_name=`$CAT "unique_id"`
	echo scsi@$scsi_name
        break
    done
}

#
# goto_dir
# This looks for a given file in a given directory or any parents of the 
# given directory.
#
# $1 starting directory
# $2 file to search for
#  
goto_dir()
{
    local start_dir=$1
    local fname=$2
    local found=0

    cd $start_dir
    while [[ $PWD != "/" ]]; do
	ls 2>/dev/null | grep $fname >/dev/null 2>&1
	if [[ $? -eq 0 ]]; then
	    found=1
	    break
	fi
	cd ..
    done

    if [[ $found -eq 0 ]]; then
	err $ERR_NO_SYSFS_DEVINFO
    fi
}

#
# logical_to_ofpathname
# Conversion for logical device name to an Open Firmware device path
#
logical_to_ofpathname()
{
    DEVICE=${DEVNAME##*/}
    DEVNODE=${DEVICE%%[0-9]*}

    case $DEVICE in
	eth*)	l2of_ethernet ;;
    	sd*)	l2of_scsi ;;
	sr*)	l2of_scsi
		OF_PATH=$OF_PATH:1,\\ppc\\bootinfo.txt ;;
	hd*)    l2of_cdrom
		OF_PATH=$OF_PATH:1,\\ppc\\bootinfo.txt ;;
	fd*)	echo "no fd yet" ;;
    esac

    if [[ -z $OF_PATH ]]; then
        err $ERR_NO_OFPATH
    fi

    echo $OF_PATH
}

#
# l2of_cdrom
# Conversion routine for logical => OF path of cdrom devices
#
l2of_cdrom()
{
    cd /sys/block/$DEVICE
    local link=`get_link "device"`
    if [[ -z $link ]]; then 
	err $ERR_NO_SYSFS_DEVINFO
    fi
    cd $link

    # get the device number
    local devno=${PWD##/*/}
    devno=${devno##*\.}

    goto_dir $PWD "devspec"

    OF_PATH=`$CAT $PWD/devspec`
    if [[ -z $OF_PATH ]]; then
        err $ERR_NO_OFPATH
    fi

    OF_PATH=$OF_PATH/disk@$devno
}

#
# l2of_ethernet
# Conversion routine for logical => OF path of ethernet devices
#
l2of_ethernet()
{
    local sysfs_eth=`$FIND /sys -name $DEVICE 2> /dev/null`
    if [[ -z $sysfs_eth ]]; then
	err $ERR_NOT_CONFIG
    fi

    OF_PATH=`$CAT $sysfs_eth/device/devspec`
    if [[ -z $OF_PATH ]]; then
	err $ERR_NO_OFPATH
    fi
}

#
# l2of_scsi
# Converion routine for logical => OF path of scsi devices
#
l2of_scsi()
{
    local sysfs_sd=`$FIND /sys -name $DEVICE 2>/dev/null`
    if [[ -z $sysfs_sd ]]; then
	err $ERR_NOT_CONFIG
    fi

    # Move up directories until we find onew with a device link
    goto_dir $sysfs_sd "device"

    # follow the 'device' link
    local link=`get_link "device"`
    if [[ -z $link ]]; then 
	err $ERR_NO_SYSFS_DEVINFO
    fi

    get_hbtl $link 
    cd $link

    # save the name of the current directory, we may need it later...
    local device_dir=${PWD##/*/}

    # move up directories until we find one with devspec information
    goto_dir $PWD "devspec"

    OF_PATH=`$CAT $PWD/devspec`
    if [[ -z $OF_PATH ]]; then
        err $ERR_NO_OFPATH
    fi

    local vdev=${OF_PATH%/*}

    if [[ $vdev = "/vdevice" ]]; then
	# get the v-device data
	local i vdiskno
	cd host*
	for i in *:*:*:*; do
	    vdiskno=`get_vdisk_no $i`
	done

	OF_PATH=$OF_PATH/disk\@$vdiskno

    else
        # make sure the "scsi" information is on the end of the path
        local scsi_name=${OF_PATH##/*/}
        scsi_name=${scsi_name%%@*}
        if [[ $scsi_name != "scsi" ]]; then
	    scsi_name=`get_scsi_host $device_dir`
	    OF_PATH=$OF_PATH/$scsi_name
        fi

        OF_PATH=$OF_PATH/sd@$TARGET,$LUN
    fi

}

#
# ofpathname_to_logical
# Conversion for Open Firmware device paths to logical device names
#
ofpathname_to_logical()
{
    DEVPATH=${DEVNAME%/*}
    DEVICE=${DEVNAME##/*/}
    DEVTYPE=${DEVICE%\@*}

    # Remove any possible cdrom data from DEVICE
    if [[ ${DEVICE##*,} = "\ppc\bootinfo.txt" ]]; then
        DEVICE=${DEVICE%,*}
    fi

    case $DEVTYPE in
	sd*           )  of2l_scsi ;;
	v-scsi | disk )  of2l_vscsi ;;
        eth* | l-lan  )  of2l_ethernet ;;
	disk*         )  of2l_cdrom ;;
    esac

    if [[ -z $LOGICAL_DEVNAME ]]; then
        err $ERR_NO_LOGDEV
    fi

    # See if this device is the cdrom
    if [[ `get_link "/dev/cdrom"` = $LOGICAL_DEVNAME ]]; then
	LOGICAL_DEVNAME="cdrom"
    fi

    echo $LOGICAL_DEVNAME
}

#
# of2l_cdrom
# Conversion routine for OF path => logical name for cdrom devices
#
of2l_cdrom()
{
    local dir

    for dir in `$FIND /sys/block -name hd*`; do
	# get devno
	local devno=${DEVICE##*@}
	devno=${devno%%:*}

	cd $dir
	local link=`get_link "device"`
        if [[ -z $link ]]; then 
	    err $ERR_NO_SYSFS_DEVINFO
        fi

	cd $link

	# see if this is the correct device
	local this_devno=${PWD##*\.}
	if [[ $devno -eq $this_devno ]]; then
	    goto_dir $PWD "devspec"
	    local devspec=`$CAT ./devspec 2>/dev/null`
	    if [[ $devspec = $DEVPATH ]]; then
		LOGICAL_DEVNAME="${dir##*/}"
		break
	    fi
	fi
    done
}

#
# of2l_ethernet
# Conversion routine for OF path => logical names of ethernet devices
#
of2l_ethernet()
{
    local dir

    # strip off ip info if present
    local devname=${DEVNAME%%:*}

    for dir in `$FIND /sys/class/net -name eth*`; do
	goto_dir $dir device

	local link=`get_link "device"`
        if [[ -z $link ]]; then 
	    err $ERR_NO_SYSFS_DEVINFO
        fi

	cd $link
	local devspec=`$CAT ./devspec 2>/dev/null`
	if [[ $devspec = $devname ]]; then
	    LOGICAL_DEVNAME="${dir##*/}"
	    return
	fi
    done
}

#
# of2l_vscsi
# Conversion routine for OF path => logical names of virtual scsi devices
#
of2l_vscsi()
{
    DEV_HBTL_NO=${DEVICE##*\@}

    local dir
    for dir in `$FIND /sys/block -name "s[dr]*"`; do
	# go up to find directory with 'device' link
	goto_dir $dir "device"

	local link=`get_link "device"`	
        if [[ -z $link ]]; then 
	    err $ERR_NO_SYSFS_DEVINFO
        fi

	local vdiskno=`get_vdisk_no $link`

	cd $link	
	if [[ $vdiskno = $DEV_HBTL_NO ]]; then
	    goto_dir $PWD "devspec"
	    local devspec=`$CAT ./devspec 2>/dev/null`
	    if [[ $devspec = $DEVPATH ]]; then
		LOGICAL_DEVNAME=${dir##/*/}
		return
	    fi
	fi
    done
}
	
#
# of2l_scsi
# Conversion routine for OF path => logical names of scsi devices
#
of2l_scsi()
{
    DEV_TARGET=${DEVICE##*\@}
    DEV_TARGET=${DEV_TARGET%%,*}
    DEV_LUN=${DEVICE##*,}

    # At this point DEV_LUN may be in the form X:Y, we're only interested
    # in the X component.         
    DEV_LUN=${DEV_LUN%%:*}

    local dir
    for dir in `$FIND /sys/block -name s[dr]*`; do
	# go up to find directory with 'device' link
	goto_dir $dir "device"

	local link=`get_link "device"`	
        if [[ -z $link ]]; then 
	    err $ERR_NO_SYSFS_DEVINFO
        fi

	get_hbtl $link
	cd $link

        # save the name of the current directory, we may need it later...
        local device_dir=${PWD##/*/}

	if [[ $TARGET = $DEV_TARGET && $LUN = $DEV_LUN ]]; then
	    goto_dir $PWD "devspec"
	    local devspec=`$CAT ./devspec 2>/dev/null`
	    if [[ $devspec = $DEVPATH ]]; then
	        LOGICAL_DEVNAME="${dir##*/}"
		return
	    fi

	    local scsi_name=${devspec##/*/}
	    scsi_name=${scsi_name%%@*}
	    if [[ $scsi_name != "scsi" ]]; then
		scsi_name=`get_scsi_host $device_dir`
		devspec=$devspec/$scsi_name
	        if [[ $devspec = $DEVPATH ]]; then
	            LOGICAL_DEVNAME="${dir##*/}"
		    return
	        fi
	    fi
	fi
    done
}

#
# Main 
#

if [[ "$#" -eq 0 ]]; then
    usage
    exit 0
fi

# default: convert logical => OFpath
do_of2l=0

getopt -o "l:Vqh" -l "help,version,quiet" $@ > /dev/null 2>&1
while [[ -n $1 ]]; do
    case "$1" in
        -l) 
	    do_of2l=1 
	    DEVNAME=$2
	    shift ;;
	-V | --version) 
	    show_version 
	    exit 0 ;;
	-q | --quiet)
	    be_quiet=1 ;;
	-h | --help) 
	    usage
	    exit 0 ;;
	*)
	    DEVNAME=$1 ;;
    esac
    shift
done

# double check device name
if [[ -z $DEVNAME ]]; then
    usage
    exit 1
fi

# follow any links to the real device
while [[ -L $DEVNAME ]]; do
    DEVNAME=`get_link $DEVNAME`
done
while [[ -L /dev/$DEVNAME ]]; do
    DEVNAME=`get_link /dev/$DEVNAME`
done

# We need sysfs
if [[ ! -d "/sys" ]]; then
    err $ERR_NO_SYSFS
    exit 1
fi


if [[ $do_of2l = "0" ]]; then
    # logical devname => OF pathname
    logical_to_ofpathname
else
    # OF pathnmae => logical devname
    ofpathname_to_logical 
fi

exit 0
